API

pyPRUF.fuzzy_logic

FuzzyAnd

Bases: FuzzyBinaryOperator

A class that defines the AND fuzzy binary operator and its truth functions, or t-norms, inheriting from FuzzyBinaryOperator Enum class.

This class, currently, includes common t-norms, or AND truth functions, such as the minimum, Łukasiewicz, algebraic product, and drastic product.

Attributes:
  • MIN (function) –

    A static method representing the minimum t-norm, which is the most common fuzzy 'AND' operation.

  • LUKASIEWICZ (function) –

    A static method implementing the Łukasiewicz t-norm, which is defined as max(a + b - 1, 0).

  • ALGEBRAIC_PRODUCT (function) –

    A static method implementing the algebraic product t-norm, defined as a * b.

  • DRASTIC_PRODUCT (function) –

    A static method implementing the drastic product t-norm, which returns 0 unless one of the values is 1.

Examples:

>>> result = FuzzyAnd.MIN(0.7, 0.8)
>>> print(result)
0.7
>>> result = FuzzyAnd.LUKASIEWICZ(0.7, 0.5)
>>> print(result)
0.2
>>> result = FuzzyAnd.ALGEBRAIC_PRODUCT(0.7, 0.8)
>>> print(result)
0.56
>>> result = FuzzyAnd.DRASTIC_PRODUCT(0.7, 0.8)
>>> print(result)
0.0
Source code in pyPRUF/fuzzy_logic.py
class FuzzyAnd(FuzzyBinaryOperator):
    """
    A class that defines the AND fuzzy binary operator
    and its truth functions, or t-norms, inheriting from
    `FuzzyBinaryOperator` `Enum` class.

    This class, currently, includes common t-norms, or AND truth functions,
    such as the minimum, Łukasiewicz, algebraic product, and drastic product.

    Attributes:
        MIN (function):
            A static method representing the minimum t-norm,
            which is the most common fuzzy 'AND' operation.
        LUKASIEWICZ (function):
            A static method implementing the
            Łukasiewicz t-norm, which is defined as `max(a + b - 1, 0)`.
        ALGEBRAIC_PRODUCT (function):
            A static method implementing the
            algebraic product t-norm, defined as `a * b`.
        DRASTIC_PRODUCT (function):
            A static method implementing the drastic
            product t-norm, which returns 0 unless one of the values is 1.

    Examples:
        >>> result = FuzzyAnd.MIN(0.7, 0.8)
        >>> print(result)
        0.7

        >>> result = FuzzyAnd.LUKASIEWICZ(0.7, 0.5)
        >>> print(result)
        0.2

        >>> result = FuzzyAnd.ALGEBRAIC_PRODUCT(0.7, 0.8)
        >>> print(result)
        0.56

        >>> result = FuzzyAnd.DRASTIC_PRODUCT(0.7, 0.8)
        >>> print(result)
        0.0
    """

    MIN = min

    @member
    def LUKASIEWICZ(a: float, b: float) -> float:
        """
        A binary function that implements the Lukasiewicz AND
        truth function.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: `max(a + b - 1.0, 0.0)`.
        """
        # return max(float(Decimal(a + b) - Decimal(1.0)), 0.0)
        return max(a + b - 1.0, 0.0)

    @member
    def ALGEBRAIC_PRODUCT(a: float, b: float) -> float:
        """
        Implements the algebraic product AND
        truth function.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: a * b.
        """
        # return float(Decimal(a) * Decimal(b))
        return a * b

    @member
    def DRASTIC_PRODUCT(a: float, b: float) -> float:
        """
        Implements the drastic product AND
        truth function.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: .0 if 1.0 != a and 1.0 != b
                and min(a, b) otherwise.
        """
        if 1.0 != a and 1.0 != b:
            return 0.0
        return min(a, b)

    def __call__(self, a: float, b: float) -> float:
        """
        Calculates the fuzzy AND of a and b.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: the result of the AND operation.

        Raises:
            AssertionError: If `a` and `b` are not of type
                `float` in the interval [0, 1].
        """
        assert isinstance(a, float) and isinstance(b, float), "'a' and 'b' values must be floats!"
        assert 0.0 <= a and a <= 1.0 and 0.0 <= b and b <= 1.0, "'a' and 'b' must be between [0, 1]!"
        return self.value(a, b)

ALGEBRAIC_PRODUCT(a, b)

Implements the algebraic product AND truth function.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    a * b.

Source code in pyPRUF/fuzzy_logic.py
@member
def ALGEBRAIC_PRODUCT(a: float, b: float) -> float:
    """
    Implements the algebraic product AND
    truth function.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: a * b.
    """
    # return float(Decimal(a) * Decimal(b))
    return a * b

DRASTIC_PRODUCT(a, b)

Implements the drastic product AND truth function.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    .0 if 1.0 != a and 1.0 != b and min(a, b) otherwise.

Source code in pyPRUF/fuzzy_logic.py
@member
def DRASTIC_PRODUCT(a: float, b: float) -> float:
    """
    Implements the drastic product AND
    truth function.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: .0 if 1.0 != a and 1.0 != b
            and min(a, b) otherwise.
    """
    if 1.0 != a and 1.0 != b:
        return 0.0
    return min(a, b)

LUKASIEWICZ(a, b)

A binary function that implements the Lukasiewicz AND truth function.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    max(a + b - 1.0, 0.0).

Source code in pyPRUF/fuzzy_logic.py
@member
def LUKASIEWICZ(a: float, b: float) -> float:
    """
    A binary function that implements the Lukasiewicz AND
    truth function.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: `max(a + b - 1.0, 0.0)`.
    """
    # return max(float(Decimal(a + b) - Decimal(1.0)), 0.0)
    return max(a + b - 1.0, 0.0)

__call__(a, b)

Calculates the fuzzy AND of a and b.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    the result of the AND operation.

Raises:
  • AssertionError

    If a and b are not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def __call__(self, a: float, b: float) -> float:
    """
    Calculates the fuzzy AND of a and b.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: the result of the AND operation.

    Raises:
        AssertionError: If `a` and `b` are not of type
            `float` in the interval [0, 1].
    """
    assert isinstance(a, float) and isinstance(b, float), "'a' and 'b' values must be floats!"
    assert 0.0 <= a and a <= 1.0 and 0.0 <= b and b <= 1.0, "'a' and 'b' must be between [0, 1]!"
    return self.value(a, b)

FuzzyBinaryOperator

Bases: FuzzyOperator

An abstract base class that defines a generic fuzzy binary operator by extending the FuzzyOperator Enum class. A fuzzy binary operator is a function that takes two memberships values and returns another membership value.

Source code in pyPRUF/fuzzy_logic.py
class FuzzyBinaryOperator(FuzzyOperator):
    """
    An abstract base class that defines a generic fuzzy binary operator
    by extending the `FuzzyOperator` `Enum` class.
    A fuzzy binary operator is a function that takes two
    memberships values and returns another membership value.
    """

    @abstractmethod
    def __call__(self, a: float, b: float) -> float:
        """
        Perform the fuzzy binary operation.

        This method must be overridden by subclasses to define the
        specific behavior of the fuzzy binary operator.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: the result of the operation
        """
        pass

__call__(a, b) abstractmethod

Perform the fuzzy binary operation.

This method must be overridden by subclasses to define the specific behavior of the fuzzy binary operator.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    the result of the operation

Source code in pyPRUF/fuzzy_logic.py
@abstractmethod
def __call__(self, a: float, b: float) -> float:
    """
    Perform the fuzzy binary operation.

    This method must be overridden by subclasses to define the
    specific behavior of the fuzzy binary operator.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: the result of the operation
    """
    pass

FuzzyLogic

A static class that encapsulates and manages all possible truth functions for fuzzy AND, OR, and NOT operations within fuzzy logic.

The FuzzyLogic class provides static methods to perform fuzzy logical operations, such as AND, OR, and NOT, by utilizing configurable truth functions. These functions are used in the DiscreteFuzzySet class to perform operations involving fuzzy logic.

By using this class, it is possible to change the behavior of the DiscreteFuzzySet class by altering the current truth functions for the fuzzy AND, OR, and NOT operations.

Source code in pyPRUF/fuzzy_logic.py
class FuzzyLogic():
    """
    A static class that encapsulates and manages all possible truth functions 
    for fuzzy AND, OR, and NOT operations within fuzzy logic.

    The `FuzzyLogic` class provides static methods to perform fuzzy logical operations, 
    such as AND, OR, and NOT, by utilizing configurable truth functions. These functions 
    are used in the `DiscreteFuzzySet` class to perform operations involving fuzzy logic.

    By using this class, it is possible to change the behavior of the `DiscreteFuzzySet` 
    class by altering the current truth functions for the fuzzy AND, OR, and NOT operations.
    """

    __and_fun: FuzzyAnd = FuzzyAnd.MIN
    __or_fun: FuzzyOr = FuzzyOr.MAX
    __not_fun: FuzzyNot = FuzzyNot.STANDARD

    def and_fun(a: float, b: float) -> float:
        """
        Calls the currently `FuzzyAnd` truth function set with
        `a` and `b` as parameters.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: the result of the currently
                `FuzzyAnd` truth function set.

        Raises:
            AssertionError: If `a` and `b` are not of type
                `float` in the interval [0, 1].
        """
        return FuzzyLogic.__and_fun(a, b)

    def or_fun(a: float, b: float) -> float:
        """
        Calls the currently `FuzzyOr` truth function set with
        `a` and `b` as parameters.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: the result of the currently
                `FuzzyOr` truth function set.

        Raises:
            AssertionError: If `a` and `b` are not of type
                `float` in the interval [0, 1].
        """
        return FuzzyLogic.__or_fun(a, b)

    def not_fun(a: float) -> float:
        """
        Calls the currently `FuzzyNot` truth function set with
        `a` as parameter.

        Parameters:
            a (float): The membership value on which the
                operation is to be carried out.

        Returns:
            float: the result of the currently
                FuzzyNot truth function set.

        Raises:
            AssertionError: If `a` and `b` are not of type
                float in the interval [0, 1].
        """
        return FuzzyLogic.__not_fun(a)

    def set_and_fun(and_fun: FuzzyAnd) -> None:
        """
        Set `and_fun` as the current `FuzzyAnd` truth function.

        Parameters:
            and_fun (FuzzyAnd): The `FuzzyAnd` truth function.

        Raises:
            AssertionError: If `and_fun`
                is not of type `FuzzyAnd`.
        """
        assert isinstance(and_fun, FuzzyAnd), "'and_fun' must be of type 'FuzzyAnd'!"
        FuzzyLogic.__and_fun = and_fun

    def set_or_fun(or_fun: FuzzyOr) -> None:
        """
        Set `or_fun` as the current `FuzzyOr` truth function.

        Parameters:
            or_fun (FuzzyOr): The `FuzzyOr` truth function.

        Raises:
            AssertionError: If `or_fun`
                is not of type `FuzzyOr`.
        """
        assert isinstance(or_fun, FuzzyOr), "'or_fun' must be of type 'FuzzyOr'!"
        FuzzyLogic.__or_fun = or_fun

    def set_not_fun(not_fun: FuzzyNot) -> None:
        """
        Set `not_fun` as the current `FuzzyNot` truth function.

        Parameters:
            not_fun (FuzzyNot): The `FuzzyNot` truth function.

        Raises:
            AssertionError: If `not_fun`
                is not of type `FuzzyNot`.
        """
        assert isinstance(not_fun, FuzzyNot), "'not_fun' must be of type 'FuzzyNot'!"
        FuzzyLogic.__not_fun = not_fun

and_fun(a, b)

Calls the currently FuzzyAnd truth function set with a and b as parameters.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    the result of the currently FuzzyAnd truth function set.

Raises:
  • AssertionError

    If a and b are not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def and_fun(a: float, b: float) -> float:
    """
    Calls the currently `FuzzyAnd` truth function set with
    `a` and `b` as parameters.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: the result of the currently
            `FuzzyAnd` truth function set.

    Raises:
        AssertionError: If `a` and `b` are not of type
            `float` in the interval [0, 1].
    """
    return FuzzyLogic.__and_fun(a, b)

not_fun(a)

Calls the currently FuzzyNot truth function set with a as parameter.

Parameters:
  • a (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    the result of the currently FuzzyNot truth function set.

Raises:
  • AssertionError

    If a and b are not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def not_fun(a: float) -> float:
    """
    Calls the currently `FuzzyNot` truth function set with
    `a` as parameter.

    Parameters:
        a (float): The membership value on which the
            operation is to be carried out.

    Returns:
        float: the result of the currently
            FuzzyNot truth function set.

    Raises:
        AssertionError: If `a` and `b` are not of type
            float in the interval [0, 1].
    """
    return FuzzyLogic.__not_fun(a)

or_fun(a, b)

Calls the currently FuzzyOr truth function set with a and b as parameters.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    the result of the currently FuzzyOr truth function set.

Raises:
  • AssertionError

    If a and b are not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def or_fun(a: float, b: float) -> float:
    """
    Calls the currently `FuzzyOr` truth function set with
    `a` and `b` as parameters.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: the result of the currently
            `FuzzyOr` truth function set.

    Raises:
        AssertionError: If `a` and `b` are not of type
            `float` in the interval [0, 1].
    """
    return FuzzyLogic.__or_fun(a, b)

set_and_fun(and_fun)

Set and_fun as the current FuzzyAnd truth function.

Parameters:
  • and_fun (FuzzyAnd) –

    The FuzzyAnd truth function.

Raises:
  • AssertionError

    If and_fun is not of type FuzzyAnd.

Source code in pyPRUF/fuzzy_logic.py
def set_and_fun(and_fun: FuzzyAnd) -> None:
    """
    Set `and_fun` as the current `FuzzyAnd` truth function.

    Parameters:
        and_fun (FuzzyAnd): The `FuzzyAnd` truth function.

    Raises:
        AssertionError: If `and_fun`
            is not of type `FuzzyAnd`.
    """
    assert isinstance(and_fun, FuzzyAnd), "'and_fun' must be of type 'FuzzyAnd'!"
    FuzzyLogic.__and_fun = and_fun

set_not_fun(not_fun)

Set not_fun as the current FuzzyNot truth function.

Parameters:
  • not_fun (FuzzyNot) –

    The FuzzyNot truth function.

Raises:
  • AssertionError

    If not_fun is not of type FuzzyNot.

Source code in pyPRUF/fuzzy_logic.py
def set_not_fun(not_fun: FuzzyNot) -> None:
    """
    Set `not_fun` as the current `FuzzyNot` truth function.

    Parameters:
        not_fun (FuzzyNot): The `FuzzyNot` truth function.

    Raises:
        AssertionError: If `not_fun`
            is not of type `FuzzyNot`.
    """
    assert isinstance(not_fun, FuzzyNot), "'not_fun' must be of type 'FuzzyNot'!"
    FuzzyLogic.__not_fun = not_fun

set_or_fun(or_fun)

Set or_fun as the current FuzzyOr truth function.

Parameters:
  • or_fun (FuzzyOr) –

    The FuzzyOr truth function.

Raises:
  • AssertionError

    If or_fun is not of type FuzzyOr.

Source code in pyPRUF/fuzzy_logic.py
def set_or_fun(or_fun: FuzzyOr) -> None:
    """
    Set `or_fun` as the current `FuzzyOr` truth function.

    Parameters:
        or_fun (FuzzyOr): The `FuzzyOr` truth function.

    Raises:
        AssertionError: If `or_fun`
            is not of type `FuzzyOr`.
    """
    assert isinstance(or_fun, FuzzyOr), "'or_fun' must be of type 'FuzzyOr'!"
    FuzzyLogic.__or_fun = or_fun

FuzzyNot

Bases: FuzzyUnaryOperator

A class that defines the NOT fuzzy binary operator and its truth functions inheriting from FuzzyUnaryOperator Enum class.

The FuzzyNot class provides various methods to perform fuzzy 'NOT' operations, which are used to invert the membership values of fuzzy sets. This class includes common operations such as the standard negation and cosine-based negation.

Attributes:
  • STANDARD (function) –

    A static method representing the standard negation, defined as 1.0 - value.

  • COSINE (function) –

    A static method implementing cosine-based negation, defined as (1.0 + cos(π * value)) / 2.0.

Examples:

>>> result = FuzzyNot.STANDARD(0.3)
>>> print(result)
0.7
>>> result = FuzzyNot.COSINE(0.3)
>>> print(result)
0.9045084971874737
Source code in pyPRUF/fuzzy_logic.py
class FuzzyNot(FuzzyUnaryOperator):
    """
    A class that defines the NOT fuzzy binary operator
    and its truth functions inheriting from
    `FuzzyUnaryOperator` `Enum` class.

    The `FuzzyNot` class provides various methods to perform fuzzy 'NOT' operations, which are used to 
    invert the membership values of fuzzy sets. This class includes common operations such as the standard 
    negation and cosine-based negation.

    Attributes:
        STANDARD (function):
            A static method representing the standard negation, defined as `1.0 - value`.
        COSINE (function):
            A static method implementing cosine-based negation, defined as `(1.0 + cos(π * value)) / 2.0`.

    Examples:
        >>> result = FuzzyNot.STANDARD(0.3)
        >>> print(result)
        0.7

        >>> result = FuzzyNot.COSINE(0.3)
        >>> print(result)
        0.9045084971874737
    """

    @member
    def STANDARD(value: float) -> float:
        """
        Implements the standard NOT
        truth function.

        Parameters:
            value (float): The membership value on which the
                operation is to be carried out.

        Returns:
            float: `1.0 - value`.
        """
        # return float(Decimal(1.0) - Decimal(value))
        return 1.0 - value

    @member
    def COSINE(value: float) -> float:
        """
        Implements the standard NOT
        truth function.

        Parameters:
            value (float): The membership value on which the
                operation is to be carried out.

        Returns:
            float: `(1.0 + math.cos(math.pi * value)) / 2.0`.
        """
        # return (1.0 + math.cos(float(Decimal(math.pi) * Decimal(value)))) / 2.0
        return (1.0 + math.cos(math.pi * value)) / 2.0

    def __call__(self, a: float) -> float:
        """
        Calculates the fuzzy NOT of `a`.

        Parameters:
            a (float): The membership value on which the
                operation is to be carried out.

        Returns:
            the result of the NOT operation.

        Raises:
            AssertionError: If `a` is not of type
                `float` in the interval [0, 1].
        """
        assert isinstance(a, float), "'a' value must be float!"
        assert 0.0 <= a and a <= 1.0, "'a' must be between [0, 1]!"
        return self.value(a)

COSINE(value)

Implements the standard NOT truth function.

Parameters:
  • value (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    (1.0 + math.cos(math.pi * value)) / 2.0.

Source code in pyPRUF/fuzzy_logic.py
@member
def COSINE(value: float) -> float:
    """
    Implements the standard NOT
    truth function.

    Parameters:
        value (float): The membership value on which the
            operation is to be carried out.

    Returns:
        float: `(1.0 + math.cos(math.pi * value)) / 2.0`.
    """
    # return (1.0 + math.cos(float(Decimal(math.pi) * Decimal(value)))) / 2.0
    return (1.0 + math.cos(math.pi * value)) / 2.0

STANDARD(value)

Implements the standard NOT truth function.

Parameters:
  • value (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    1.0 - value.

Source code in pyPRUF/fuzzy_logic.py
@member
def STANDARD(value: float) -> float:
    """
    Implements the standard NOT
    truth function.

    Parameters:
        value (float): The membership value on which the
            operation is to be carried out.

    Returns:
        float: `1.0 - value`.
    """
    # return float(Decimal(1.0) - Decimal(value))
    return 1.0 - value

__call__(a)

Calculates the fuzzy NOT of a.

Parameters:
  • a (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float

    the result of the NOT operation.

Raises:
  • AssertionError

    If a is not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def __call__(self, a: float) -> float:
    """
    Calculates the fuzzy NOT of `a`.

    Parameters:
        a (float): The membership value on which the
            operation is to be carried out.

    Returns:
        the result of the NOT operation.

    Raises:
        AssertionError: If `a` is not of type
            `float` in the interval [0, 1].
    """
    assert isinstance(a, float), "'a' value must be float!"
    assert 0.0 <= a and a <= 1.0, "'a' must be between [0, 1]!"
    return self.value(a)

FuzzyOperator

Bases: Enum

An abstract base class that defines a generic fuzzy operator. A fuzzy operator is a function that takes memberships values and returns another membership value.

This class is an enumeration (Enum) that requires derived classes to implement the __call__() method, which should return a float. In this way, it is possible to provide for each fuzzy operator, several implementations (or truth functions) that will correspond to the values of the Enum and call them all together with the __call__() method by using its operator ().

This is useful in fuzzy logic where operators, such as fuzzy AND, OR, and NOT, have many truth functions such as t-norms and t-conorms.

Source code in pyPRUF/fuzzy_logic.py
class FuzzyOperator(Enum):
    """
    An abstract base class that defines a generic fuzzy operator.
    A fuzzy operator is a function that takes memberships values
    and returns another membership value.

    This class is an enumeration (`Enum`) that requires derived classes
    to implement the `__call__()` method, which should return a `float`.
    In this way, it is possible to provide for each fuzzy operator,
    several implementations (or truth functions) that will correspond
    to the values of the Enum and call them all together with
    the `__call__()` method by using its operator `()`.

    This is useful in fuzzy logic where operators, such
    as fuzzy AND, OR, and NOT, have many truth functions such as
    t-norms and t-conorms.
    """

    @abstractmethod
    def __call__(self) -> float:
        """
        Perform the fuzzy operation.

        This method must be overridden by subclasses to define the
        specific behavior of the fuzzy operator.

        Returns:
            float: the result of the operation
        """
        pass

__call__() abstractmethod

Perform the fuzzy operation.

This method must be overridden by subclasses to define the specific behavior of the fuzzy operator.

Returns:
  • float( float ) –

    the result of the operation

Source code in pyPRUF/fuzzy_logic.py
@abstractmethod
def __call__(self) -> float:
    """
    Perform the fuzzy operation.

    This method must be overridden by subclasses to define the
    specific behavior of the fuzzy operator.

    Returns:
        float: the result of the operation
    """
    pass

FuzzyOr

Bases: FuzzyBinaryOperator

A class that defines the OR fuzzy binary operator and its truth functions by inheriting from FuzzyBinaryOperator Enum class.

This class, currently, includes common t-conorms such as the maximum, Łukasiewicz, algebraic sum, and drastic sum.

Attributes:
  • MAX (function) –

    A static method representing the maximum t-conorm, which is the most common fuzzy 'OR' operation.

  • LUKASIEWICZ (function) –

    A static method implementing the Łukasiewicz t-conorm, defined as min(a + b, 1.0).

  • ALGEBRAIC_SUM (function) –

    A static method implementing the algebraic sum t-conorm, defined as a + b - a * b.

  • DRASTIC_SUM (function) –

    A static method implementing the drastic sum t-conorm, which returns 1.0 unless one of the values is 0.

Examples:

>>> result = FuzzyOr.MAX(0.3, 0.8)
>>> print(result)
0.8
>>> result = FuzzyOr.LUKASIEWICZ(0.7, 0.5)
>>> print(result)
1.0
>>> result = FuzzyOr.ALGEBRAIC_SUM(0.3, 0.8)
>>> print(result)
0.86
>>> result = FuzzyOr.DRASTIC_SUM(0.0, 0.8)
>>> print(result)
0.8
Source code in pyPRUF/fuzzy_logic.py
class FuzzyOr(FuzzyBinaryOperator):
    """
    A class that defines the OR fuzzy binary operator
    and its truth functions by inheriting from
    `FuzzyBinaryOperator` `Enum` class.

    This class, currently, includes common t-conorms such as the maximum, 
    Łukasiewicz, algebraic sum, and drastic sum.

    Attributes:
        MAX (function):
            A static method representing the maximum t-conorm,
            which is the most common fuzzy 'OR' operation.
        LUKASIEWICZ (function):
            A static method implementing the Łukasiewicz t-conorm,
            defined as `min(a + b, 1.0)`.
        ALGEBRAIC_SUM (function):
            A static method implementing the algebraic sum t-conorm,
            defined as `a + b - a * b`.
        DRASTIC_SUM (function):
            A static method implementing the drastic sum t-conorm,
            which returns 1.0 unless one of the values is 0.

    Examples:
        >>> result = FuzzyOr.MAX(0.3, 0.8)
        >>> print(result)
        0.8

        >>> result = FuzzyOr.LUKASIEWICZ(0.7, 0.5)
        >>> print(result)
        1.0

        >>> result = FuzzyOr.ALGEBRAIC_SUM(0.3, 0.8)
        >>> print(result)
        0.86

        >>> result = FuzzyOr.DRASTIC_SUM(0.0, 0.8)
        >>> print(result)
        0.8
    """

    MAX = max

    @member
    def LUKASIEWICZ(a: float, b: float) -> float: # sempre > .0
        """
        Implements the Lukasiewicz OR
        truth function.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            float: `min(a + b, 1.0)`.
        """
        return min(a + b, 1.0)

    @member
    def ALGEBRAIC_SUM(a: float, b: float) -> float: # sempre > .0
        """
        Implements the algebraic sum OR
        truth function.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            a + b - a * b.
        """
        # return float(Decimal(a + b) - Decimal(a) * Decimal(b))
        return a + b - a * b

    @member
    def DRASTIC_SUM(a: float, b: float) -> float:
        """
        Implements the drastic sum OR
        truth function.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            1.0 if .0 != a and .0 != b
            and `max(a, b)` otherwise.
        """
        if .0 != a and .0 != b:
            return 1.0
        return max(a, b)

    def __call__(self, a: float, b: float) -> float:
        """
        Calculates the fuzzy OR of `a` and `b`.

        Parameters:
            a (float): The first membership value.
            b (float): The second membership value.

        Returns:
            the result of the OR operation.

        Raises:
            AssertionError: If `a` and `b` are not of type
                `float` in the interval [0, 1].
        """
        assert isinstance(a, float) and isinstance(b, float), "'a' and 'b' values must be floats!"
        assert 0.0 <= a and a <= 1.0 and 0.0 <= b and b <= 1.0, "'a' and 'b' must be between [0, 1]!"
        return self.value(a, b)

ALGEBRAIC_SUM(a, b)

Implements the algebraic sum OR truth function.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float

    a + b - a * b.

Source code in pyPRUF/fuzzy_logic.py
@member
def ALGEBRAIC_SUM(a: float, b: float) -> float: # sempre > .0
    """
    Implements the algebraic sum OR
    truth function.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        a + b - a * b.
    """
    # return float(Decimal(a + b) - Decimal(a) * Decimal(b))
    return a + b - a * b

DRASTIC_SUM(a, b)

Implements the drastic sum OR truth function.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float

    1.0 if .0 != a and .0 != b

  • float

    and max(a, b) otherwise.

Source code in pyPRUF/fuzzy_logic.py
@member
def DRASTIC_SUM(a: float, b: float) -> float:
    """
    Implements the drastic sum OR
    truth function.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        1.0 if .0 != a and .0 != b
        and `max(a, b)` otherwise.
    """
    if .0 != a and .0 != b:
        return 1.0
    return max(a, b)

LUKASIEWICZ(a, b)

Implements the Lukasiewicz OR truth function.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float( float ) –

    min(a + b, 1.0).

Source code in pyPRUF/fuzzy_logic.py
@member
def LUKASIEWICZ(a: float, b: float) -> float: # sempre > .0
    """
    Implements the Lukasiewicz OR
    truth function.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        float: `min(a + b, 1.0)`.
    """
    return min(a + b, 1.0)

__call__(a, b)

Calculates the fuzzy OR of a and b.

Parameters:
  • a (float) –

    The first membership value.

  • b (float) –

    The second membership value.

Returns:
  • float

    the result of the OR operation.

Raises:
  • AssertionError

    If a and b are not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def __call__(self, a: float, b: float) -> float:
    """
    Calculates the fuzzy OR of `a` and `b`.

    Parameters:
        a (float): The first membership value.
        b (float): The second membership value.

    Returns:
        the result of the OR operation.

    Raises:
        AssertionError: If `a` and `b` are not of type
            `float` in the interval [0, 1].
    """
    assert isinstance(a, float) and isinstance(b, float), "'a' and 'b' values must be floats!"
    assert 0.0 <= a and a <= 1.0 and 0.0 <= b and b <= 1.0, "'a' and 'b' must be between [0, 1]!"
    return self.value(a, b)

FuzzyUnaryOperator

Bases: FuzzyOperator

An abstract base class that defines a generic fuzzy unary operator by extending the FuzzyOperator Enum class. A fuzzy unary operator is a function that takes a membership value and returns another membership value.

Source code in pyPRUF/fuzzy_logic.py
class FuzzyUnaryOperator(FuzzyOperator):
    """
    An abstract base class that defines a generic fuzzy unary operator
    by extending the `FuzzyOperator` `Enum` class.
    A fuzzy unary operator is a function that takes a
    membership value and returns another membership value.
    """

    @abstractmethod
    def __call__(self, a: float) -> float:
        """
        Perform the fuzzy unary operation.

        This method must be overridden by subclasses to define the
        specific behavior of the fuzzy unary operator.

        Parameters:
            a (float):  The membership value on which the
                operation is to be carried out.

        Returns:
            float: the result of the operation
        """
        pass

__call__(a) abstractmethod

Perform the fuzzy unary operation.

This method must be overridden by subclasses to define the specific behavior of the fuzzy unary operator.

Parameters:
  • a (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    the result of the operation

Source code in pyPRUF/fuzzy_logic.py
@abstractmethod
def __call__(self, a: float) -> float:
    """
    Perform the fuzzy unary operation.

    This method must be overridden by subclasses to define the
    specific behavior of the fuzzy unary operator.

    Parameters:
        a (float):  The membership value on which the
            operation is to be carried out.

    Returns:
        float: the result of the operation
    """
    pass

LinguisticModifiers

Bases: FuzzyUnaryOperator

A class that defines the linguistic modifiers inheriting from FuzzyUnaryOperator Enum class. A linguistic modifier is a function that takes a membership value and returns a modified membership value according to a linguistic semantic.

The LinguisticModifiers class provides various methods to apply linguistic modifiers to fuzzy sets, which adjust the degree of membership in a fuzzy set. This class includes common modifiers such as 'VERY' (which intensifies the membership) and 'MORE_OR_LESS' (which softens the membership).

Attributes:
  • VERY (function) –

    A static method representing the 'VERY' linguistic modifier, defined as value * value.

  • MORE_OR_LESS (function) –

    A static method representing the 'MORE_OR_LESS' linguistic modifier, defined as sqrt(value).

Examples:

>>> result = LinguisticModifiers.VERY(0.7)
>>> print(result)
0.49
>>> result = LinguisticModifiers.MORE_OR_LESS(0.49)
>>> print(result)
0.7
Source code in pyPRUF/fuzzy_logic.py
class LinguisticModifiers(FuzzyUnaryOperator):
    """
    A class that defines the linguistic modifiers
    inheriting from `FuzzyUnaryOperator` `Enum` class.
    A linguistic modifier is a function that takes
    a membership value and returns a modified
    membership value according to a linguistic
    semantic.

    The `LinguisticModifiers` class provides various methods to apply linguistic modifiers to 
    fuzzy sets, which adjust the degree of membership in a fuzzy set. This class includes common 
    modifiers such as 'VERY' (which intensifies the membership) and 'MORE_OR_LESS' (which softens the membership).

    Attributes:
        VERY (function):
            A static method representing the 'VERY' linguistic modifier, defined as `value * value`.
        MORE_OR_LESS (function):
            A static method representing the 'MORE_OR_LESS' linguistic modifier, defined as `sqrt(value)`.

    Examples:
        >>> result = LinguisticModifiers.VERY(0.7)
        >>> print(result)
        0.49

        >>> result = LinguisticModifiers.MORE_OR_LESS(0.49)
        >>> print(result)
        0.7
    """

    @member
    def VERY(value: float) -> float:
        """
        Calculates the 'very' linguistic modifier value
        of the membership value value.

        Parameters:
            value (float): The membership value on which the
                operation is to be carried out.

        Returns:
            float: `value * value`
        """
        # return float(Decimal(value) * Decimal(value))
        return value * value

    @member
    def MORE_OR_LESS(value: float) -> float:
        """
        Calculates the 'more or less' linguistic modifier value
        of the membership value value.

        Parameters:
            value (float): The membership value on which the
                operation is to be carried out.

        Returns:
            float: `math.sqrt(value)`
        """
        return math.sqrt(value)

    def __call__(self, a: float) -> float:
        """
        Calculates the linguistic modifier value of `a`.

        Parameters:
            a (float): The membership value on which the
                operation is to be carried out.

        Returns:
            float: the result of the linguistic modifier applied.

        Raises:
            AssertionError: If `a` is not of type
                `float` in the interval [0, 1].
        """
        assert isinstance(a, float), "'a' value must be float!"
        assert 0.0 <= a and a <= 1.0, "'a' must be between [0, 1]!"
        return self.value(a)

MORE_OR_LESS(value)

Calculates the 'more or less' linguistic modifier value of the membership value value.

Parameters:
  • value (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    math.sqrt(value)

Source code in pyPRUF/fuzzy_logic.py
@member
def MORE_OR_LESS(value: float) -> float:
    """
    Calculates the 'more or less' linguistic modifier value
    of the membership value value.

    Parameters:
        value (float): The membership value on which the
            operation is to be carried out.

    Returns:
        float: `math.sqrt(value)`
    """
    return math.sqrt(value)

VERY(value)

Calculates the 'very' linguistic modifier value of the membership value value.

Parameters:
  • value (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    value * value

Source code in pyPRUF/fuzzy_logic.py
@member
def VERY(value: float) -> float:
    """
    Calculates the 'very' linguistic modifier value
    of the membership value value.

    Parameters:
        value (float): The membership value on which the
            operation is to be carried out.

    Returns:
        float: `value * value`
    """
    # return float(Decimal(value) * Decimal(value))
    return value * value

__call__(a)

Calculates the linguistic modifier value of a.

Parameters:
  • a (float) –

    The membership value on which the operation is to be carried out.

Returns:
  • float( float ) –

    the result of the linguistic modifier applied.

Raises:
  • AssertionError

    If a is not of type float in the interval [0, 1].

Source code in pyPRUF/fuzzy_logic.py
def __call__(self, a: float) -> float:
    """
    Calculates the linguistic modifier value of `a`.

    Parameters:
        a (float): The membership value on which the
            operation is to be carried out.

    Returns:
        float: the result of the linguistic modifier applied.

    Raises:
        AssertionError: If `a` is not of type
            `float` in the interval [0, 1].
    """
    assert isinstance(a, float), "'a' value must be float!"
    assert 0.0 <= a and a <= 1.0, "'a' must be between [0, 1]!"
    return self.value(a)

pyPRUF.fuzzy_sets

ContinuousFuzzySet

Bases: FuzzySet

A class representing a continuous fuzzy set, inheriting from FuzzySet.

WARNING: This class is not fully implemented, please check the online documentation before using it.

The ContinuousFuzzySet class is designed to handle fuzzy sets where their domain is continuous (such as Fuzzy Numbers), rather than discrete, allowing for smooth transitions between membership values.

It is defined by a MembershipFunction and a schema that together fully characterize the continous fuzzy set.

Source code in pyPRUF/fuzzy_sets.py
class ContinuousFuzzySet(FuzzySet):
    """A class representing a continuous fuzzy set,
    inheriting from `FuzzySet`.

    **WARNING**: This class is not fully implemented,
    please check the online documentation before using it.

    The `ContinuousFuzzySet` class is designed to handle
    fuzzy sets where their domain is continuous
    (such as Fuzzy Numbers), rather than discrete,
    allowing for smooth transitions between membership values.

    It is defined by a `MembershipFunction` and a schema that
    together fully characterize the continous fuzzy set.
    """

    def __init__(self, schema: tuple, mf: MembershipFunction):
        assert isinstance(schema, tuple) and len(schema) > 0, "'schema' must be a non-empty tuple of strings representing the schemas name!"
        assert isinstance(mf, MembershipFunction), "'mf' must be of type 'MembershipFunction'!"
        sorted_schema = list(schema)
        sorted_schema.sort()
        for i in range(1, len(sorted_schema)):
            assert schema[i] != schema[i - 1], "'schema' must not contains duplicates!"

        self.__schema = list(schema)
        self.__mf = mf

    def __getitem__(self, element) -> float: # membership: []
        return mf_of_tuple(element, self.__mf)

    def __setitem__(self, element, membership: float) -> None: # add/update_element: []
        pass

    def __delitem__(self, element) -> None:
        pass

    def __or__(self, set2) -> FuzzySet: # union: |, |=
        pass

    def __and__(self, set2) -> FuzzySet: # intersection: &, &=
        pass

    def __invert__(self) -> FuzzySet: # complement: ~
        pass

    def __mul__(self, set2) -> FuzzySet: # cartesian_product: *, *=
        pass

    def __matmul__(self, set2) -> FuzzySet: # natural_join: @, @=
        pass

    def __truediv__(self, set2) -> float: # proportion: /
        pass

    def projection(self, subschema: tuple, operator: FuzzyBinaryOperator) -> FuzzySet:
        pass

    def particularization(self, assignment: dict) -> FuzzySet: # particularization
        pass

    def cardinality(self) -> float:
        pass

    def mean_cardinality(self) -> float:
        pass

    def compatibility(self, reference_set) -> FuzzySet:
        pass

    def consistency(self, reference_set) -> float:
        pass

    def get_schema(self) -> Tuple[str]:
        return tuple(self.__schema)

    def rename_schema(self, ren_dict: dict) -> None:
        assert isinstance(ren_dict, dict), "'ren_dict' must be a dictionary!"
        for key, value in ren_dict.items():
            assert isinstance(key, str) and isinstance(key, str), "All keys and values in 'ren_dict' must be strings!"
            assert key in self.__schema, key + " not in this FuzzySet schema!"
            assert value not in self.__schema, value + " already in this FuzzySet schema!"
            self.__schema[self.__schema.index(key)] = value

    def select(self, condition: Callable) -> FuzzySet:
        pass

    def apply(self, operator: FuzzyUnaryOperator) -> FuzzySet:
        pass

    def extension_principle(self, function: Callable, out_schema: tuple) -> FuzzySet:
        pass

    def cylindrical_extension(self, set2: FuzzySet) -> Tuple[FuzzySet, FuzzySet]:
        pass

DiscreteFuzzySet

Bases: FuzzySet

A class that defines a generic discrete fuzzy set and all the operations that can be performed on it by extending the abstract class FuzzySet. This implementation makes the following assumptions:

  • A DiscreteFuzzySet is defined by a membership function that maps each tuple to a membership function in [0, 1] and by a fuzzy relation schema, where each attribute refers to a set in which the DiscreteFuzzySet is defined.

  • Only the elements in the support of the fuzzy set are kept as tuples of the fuzzy relations.

Source code in pyPRUF/fuzzy_sets.py
class DiscreteFuzzySet(FuzzySet):
    """A class that defines a generic discrete fuzzy set
    and all the operations that can be performed on it
    by extending the abstract class FuzzySet.
    This implementation makes the following assumptions:\n
    - A DiscreteFuzzySet is defined by a membership function
    that maps each tuple to a membership function in [0, 1]
    and by a fuzzy relation schema, where each attribute
    refers to a set in which the DiscreteFuzzySet is defined.\n
    - Only the elements in the support of the fuzzy set
    are kept as tuples of the fuzzy relations.
    """

    def __init__(self, schema: Tuple[str] = None, mf: Union[DataFrame, dict] = None):
        """Constructs the DiscreteFuzzySet object with the schema
        and membership function (mf) given in input. This constructor can initialize
        the fuzzy set from either a pandas DataFrame or a dictionary.

        Args:
            schema (tuple): A non-empty tuple of strings without
                duplicates representing the schema of the
                `DiscreteFuzzySet`
            mf: Can be `None`, a `dict` or a pandas `DataFrame`
                representing the tuples in the fuzzy relation. If
                mf is `None`, it will construct an empty fuzzy relation
                with the schema specified. If mf is a `dict`,
                it must have the tuples of the fuzzy relation as keys
                and their membership as the value of the (key, value)
                pair. Each key in the dict must be a tuple of the same
                length as the schema. If mf is a `DataFrame`, it must
                have\n
                - at least one column and at least one tuple
                - for each column a string representing the set name
                - a column named 'mu' containing the membership value of
                the corresponding tuple. If this column is missing, all
                tuples are assumed to belong to the fuzzy relation with
                membership degree 1. All memberships value in mf
                must be floats in the interval (0, 1].
        If mf is a DataFrame, it is not required to also specify the schema
        parameter. If it is done anyway, it will be ignored and the schema will
        be inferred from the DataFrame columns names. If mf is a tuple
        of strings or None, it is required to specify also the schema.

        Raises:
            AssertionError: If one of the preceding assumptions is
                breached.

        Examples:
            >>> A = DiscreteFuzzySet(('D1', 'D2'), {(1, 'val2'): 0.3, ('val1', 3.4): 0.6, (2, 'val2'): 0.9})
            >>> print(A.to_dictionary())
            {(1, 'val2'): 0.3, ('val1', 3.4): 0.6, (2, 'val2'): 0.9}
            >>> print(A.get_schema())
            ('D1', 'D2')

            >>> df = pd.DataFrame({
            ...     'x': [1, 2],
            ...     'y': [3, 4],
            ...     'mu': [0.5, 0.7]
            ... })
            >>> fuzzy_set = FuzzySet(mf=df)
            >>> print(fuzzy_set.get_schema())
            ('x', 'y')
            >>> print(fuzzy_set.to_dictionary())
            {(1, 3): 0.5, (2, 4): 0.7}
        """
        if isinstance(mf, DataFrame):
            dict_relation = {}
            for key, value in mf.to_dict().items():
                assert isinstance(key, str), "All keys in 'dict_relation' must be strings representing the variables name!"
                dict_relation[key] = tuple(value.values())
            keys = list(dict_relation.keys())
            assert len(keys) > 0, "There must be at least one column!"
            assert len(keys) != 1 or keys[0] != 'mu', "With the only column named 'mu' the resulting schema is empty!"
            length = len(dict_relation[keys[0]])
            assert length > 0, "The fuzzy set must contain at least a tuple!"

            self.__mf = {}
            for i in range(0, length):
                variables = []
                mu = 1.0
                for key in keys:
                    if key != 'mu':
                        variables.append(dict_relation[key][i])
                    else:
                        mu = dict_relation[key][i]
                        assert isinstance(mu, float) and 0.0 < mu and mu <= 1.0, "All values in 'mu' must be floats between (0, 1]!"
                self.__mf[tuple(variables)] = mu

            if 'mu' in keys:
                keys.remove('mu')
            self.__schema = keys
        else:
            assert isinstance(schema, tuple) and len(schema) > 0, "'schema' must be a non-empty tuple of strings representing the schemas name!"
            length = len(schema)
            sorted_schema = list(schema)
            sorted_schema.sort()
            for i in range(1, len(sorted_schema)):
                assert schema[i] != 'mu', "'mu' cannot be a valid attribute name in the schema!"
                assert schema[i] != schema[i - 1], "'schema' must not contains duplicates!"

            self.__mf = {}
            if mf is not None:
                assert isinstance(mf, dict), "'mf' must be 'None', a dictionary or a Dataframe!"
                for element, mu in mf.items():
                    assert isinstance(element, tuple) and len(element) == length, "All keys in 'mf' must be tuples with the same length as the schema!"
                    assert isinstance(mu, float) and 0.0 < mu and mu <= 1.0, "All memberships values must be floats between (0, 1]!"
                    self.__mf[element] = mu

            self.__schema = list(schema)

    def __getitem__(self, element) -> float: # membership: []
        """Retrieves the membership value of an element from the `DiscreteFuzzySet`.

        If the `element` exists in the set, its membership value is returned;
        otherwise, a membership value of 0.0 is returned.

        Args:
            element (tuple): The element for which to retrieve the
                membership value.

        Returns:
            float: The membership value of the `element`.
                Returns 0.0 if the `element` is not in the set.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
            >>> membership_x = set_a[('x',)]
            >>> membership_z = set_a[('z',)]
            >>> membership_fake = set_a['fake']
            >>> print(membership_x)
            0.7
            >>> print(membership_z)
            0.0
            >>> print(membership_fake)
            0.0

            In this example, the membership value for `('x',)` is retrieved and found to be 0.7.
            For the element `('z',)`, which does not exist in the set, the method returns a membership
            value of 0.0.
        """
        if element in self.__mf.keys():
            return self.__mf[element]
        return .0

    def __setitem__(self, element: tuple, membership: float) -> None: # add/update_element: []
        """Adds or updates (the membership value of) an element in the `DiscreteFuzzySet`.

        The `element` must be a tuple with the same length as the schema,
        and the `membership` must be a float value in the interval (0, 1].

        Args:
            element (tuple): The element to add or update in the
                `DiscreteFuzzySet`.
            membership (float): The membership value associated with the
                `element`.

        Raises:
            AssertionError: If `membership` is not a float in the
                interval (0, 1] or if `element` is not a tuple with
                the same length as the schema of the current fuzzy set.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7})
            >>> set_a[('y',)] = 0.5
            >>> set_a[('x',)] = 0.8
            >>> print(set_a.to_dictionary())
            {('x',): 0.8, ('y',): 0.5}

            In this example, the `element` `('y',)` is added to `set_a` with a membership value of 0.5,
            and the membership value of the existing element `('x',)` is updated to 0.8. The resulting
            set reflects these changes.
        """
        assert isinstance(membership, float), "'membership' must be a float!"
        assert .0 < membership and membership <= 1.0, "'membership' must be in (0, 1] interval!"
        assert isinstance(element, tuple), "'element' must be a tuple! "
        assert len(self.__schema) == len(element), "'element' must be a tuple of the same length of the schema!"

        self.__mf[element] = membership

    def __delitem__(self, element) -> None:
        """Deletes an element from the `DiscreteFuzzySet`.

        If the `element` exists in the set, it is deleted;
        otherwise, the operation has no effect.

        Args:
            element (tuple): The element to remove from the
                `DiscreteFuzzySet`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
            >>> del set_a[('x',)]
            >>> print(set_a.to_dictionary())
            {('y',): 0.5}

            In this example, the element `('x',)` is deleted from `set_a`. After deletion,
            the resulting set only contains the remaining element `('y',)`.
        """
        if element in self.__mf:
            del self.__mf[element]

    def __eq__(self, set2: FuzzySet) -> bool:
        """Determines whether two `DiscreteFuzzySet` instances are equal.

        Two `DiscreteFuzzySet` instances are considered equal if they have the same schema
        and identical membership values for all elements.

        Args:
            set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
                compare with `self`.

        Raises:
            AssertionError: If `set2` is not an instance of
                `DiscreteFuzzySet`.

        Returns:
            bool: `True` if `self` and `set2` are identical fuzzy sets;
                `False` otherwise.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
            >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
            >>> set_c = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
            >>> set_d = DiscreteFuzzySet(('d',), {('x',): 0.7, ('y',): 0.5})
            >>> result1 = (set_a == set_b)
            >>> result2 = (set_a == set_c)
            >>> result3 = (set_a == set_d)
            >>> print(result1)
            True
            >>> print(result2)
            False
            >>> print(result3)
            False

            In this example, `set_a` is equal to `set_b` because they have the same schema and identical
            membership values for all elements. However, `set_a` is not equal to `set_c` due to differences
            in their membership values while `set_a` is not equal to `set_d` due to to differences in
            their schemas.
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
        return set2.__mf == self.__mf and set2.get_schema() == self.get_schema()

    def __or__(self, set2: FuzzySet) -> FuzzySet: # union: |, |=
        """Computes the fuzzy union of two `DiscreteFuzzySet` instances.

        The fuzzy union is calculated by applying the `FuzzyLogic.or_fun()`
        function to the membership values of corresponding elements in both sets.

        Args:
            set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
                unite with `self`.

        Raises:
            AssertionError: If `set2` is not an instance of
                `DiscreteFuzzySet` or if `self` and `set2`
                do not have the same schema.

        Returns:
            DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
                union of `self` and `set2`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
            >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
            >>> result = set_a | set_b
            >>> print(result.to_dictionary())
            {('x',): 0.7, ('y',): 0.8}

            In this example, the union of `set_a` and `set_b` is computed by applying the `FuzzyOr.MAX`,
            set as the default `OR` truth function of the class `FuzzyLogic`,
            function to the membership values of corresponding elements. The resulting set
            contains elements with membership values that represent the maximum of the
            corresponding values in `set_a` and `set_b`.
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
        assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

        new_mf = set2.to_dictionary()
        for element, membership1 in self.__mf.items():
            new_membership = FuzzyLogic.or_fun(membership1, set2[element])
            if new_membership > .0:
                new_mf[element] = new_membership
        fs = DiscreteFuzzySet(self.get_schema(), new_mf)
        return fs

    def __and__(self, set2: FuzzySet) -> FuzzySet: # intersection: &, &=
        """Computes the intersection of two `FuzzySet` instances.

        The intersection is calculated by applying `FuzzyLogic.and_fun()`
        function to the membership values of corresponding elements in both sets.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            set2 (FuzzySet): The `FuzzySet` instance to intersect with
                `self`.

        Raises:
            AssertionError: If `set2` is not an instance of `FuzzySet`
                or if `self` and `set2` do not have the same schema.

        Returns:
            DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
                intersection of `self` and `set2`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5, ('z', ): .3})
            >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
            >>> result = set_a & set_b
            >>> print(result.to_dictionary())
            {('x',): 0.4, ('y',): 0.5}

            In this example, the intersection of `set_a` and `set_b` is computed by applying
            the `FuzzyAnd.MIN` function, set as the default `AND` truth function of the class `FuzzyLogic`,
            to the membership values of corresponding elements. The resulting set contains elements
            with membership values that represent the minimum of the
            corresponding values in `set_a` and `set_b`.
        """
        assert isinstance(set2, FuzzySet), "'set2' must be of type 'FuzzySet'!"
        assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

        new_mf = {}
        for element, membership1 in self.__mf.items():
            new_membership = FuzzyLogic.and_fun(membership1, set2[element])
            if new_membership > .0:
                new_mf[element] = new_membership
        fs = DiscreteFuzzySet(self.get_schema(), new_mf)
        return fs

    # ATTENZIONE: non corrisponde nè al complemento assoluto insiemistico nè a quello relativo
    def __invert__(self) -> FuzzySet: # fuzzy_not unary operator: ~ # NON TESTATO : Banale
        """Computes the current fuzzy set applied to the NOT FuzzyUnaryOperator.

        The result is calculated by applying `FuzzyLogic.not_fun()`
        function to the membership values of all elements in the set.

        >> **WARNING**: This operation corresponds neither to the
        set absolute complement nor to the relative complement.
        The set absolute/relative complement could be derived
        by calculating it as the difference between a fuzzy set
        (eventually the universe of discourse) and another fuzzy set.
        This correspond simply to the NOT FuzzyUnaryOperator
        applied to the tuples of this fuzzy set. Semantically
        corresponds to an application of a linguistic modifier.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Returns:
            DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
                fuzzy complement of `self`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.4}, ('z', ): 1.0)
            >>> result = ~set_a
            >>> print(result.to_dictionary())
            {('x',): 0.3, ('y',): 0.6}

            In this example, the result is computed by applying the
            `FuzzyNot.STANDARD` function, set as the default `NOT` truth
            function of the class `FuzzyLogic`, to each membership value in the set.
            The resulting set contains only the elements with a positive membership value.
        """
        new_mf = {}
        for element, membership in self.__mf.items():
            new_mf[element] = FuzzyLogic.not_fun(membership)
        fs = DiscreteFuzzySet(self.get_schema(), new_mf)
        return fs

    def __sub__(self, set2: FuzzySet) -> FuzzySet: # differenza: - # corrisponde al complemento relativo insiemistico di set2 rispetto a set1
        """Computes the difference (relative complement) between two `DiscreteFuzzySet` instances.

        The difference is calculated by applying `FuzzyLogic.and_fun()` operation between the
        membership values of `self` and the negated membership values of `set2` calculated
        using `FuzzyLogic.not_fun()`.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
                subtract from `self`.

        Raises:
            AssertionError: If `set2` is not an instance of
                `DiscreteFuzzySet` or if `self` and `set2`
                do not have the same schema.

        Returns:
            DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
                difference between `self` and `set2`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5, ('z', ): 0.3})
            >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.5}, ('z', ): 1.0)
            >>> result = set_a - set_b
            >>> print(result.to_dictionary())
            {('x',): 0.6, ('y',): 0.5}

            In this example, the result is computed using the `FuzzyAnd.MIN` and
            `FuzzyNot.STANDARD` truth functions, set as the default `AND` and `NOT` truth
            functions of the class `FuzzyLogic`.
            The resulting set contains only the elements with a positive membership value.
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
        assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

        new_mf = {}
        for element, membership1 in self.__mf.items():
            new_membership = FuzzyLogic.and_fun(membership1, FuzzyLogic.not_fun(set2[element]))
            if new_membership > .0:
                new_mf[element] = new_membership
        fs = DiscreteFuzzySet(self.get_schema(), new_mf)
        return fs

    def __mul__(self, set2: FuzzySet) -> FuzzySet: # cartesian_product: *, *=
        """Computes the Cartesian product of two `DiscreteFuzzySet` instances.

        The Cartesian product creates new elements from the two sets as their concatenation
        and applies `FuzzyLogic.and_fun()` operation to their membership values.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
                combine with.

        Raises:
            AssertionError: If `set2` is not an instance of
                `DiscreteFuzzySet` or if `self` and `set2`
                have overlapping schemas.

        Returns:
            DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
                Cartesian product of `self` and `set2`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.4})
            >>> set_b = DiscreteFuzzySet(('b',), {('1',): 0.8, ('2',): 0.6})
            >>> result = set_a * set_b
            >>> print(result.to_dictionary())
            {('x', '1'): 0.7, ('x', '2'): 0.6, ('y', '1'): 0.4, ('y', '2'): 0.4}

            In this example, the Cartesian product of `set_a` and `set_b` is computed by
            pairing each element from `set_a` with each element from `set_b`. The resulting set
            contains tuples representing all possible combinations of elements from both sets,
            with membership values calculated using the `FuzzyAnd.MIN`,
            set as the default `AND` truth function in the class `FuzzyLogic`.
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
        set1_schema = self.get_schema()
        set2_schema = set2.get_schema()
        for var in set2_schema:
            assert var not in set1_schema, "'" + var + "' is in both schemas: " + str(set1_schema) + "\n'set1' and 'set2' must have different schemas!"

        new_mf = {}
        for element1, membership1 in self.__mf.items():
            for element2, membership2 in set2.to_dictionary().items():
                new_membership = FuzzyLogic.and_fun(membership1, membership2)
                if new_membership > .0:
                    new_mf[element1 + element2] = new_membership
        fs = DiscreteFuzzySet(set1_schema + set2_schema, new_mf)
        return fs

    def __matmul__(self, set2: FuzzySet) -> FuzzySet: # natural_join: @, @=
        """Computes the natural join of two `DiscreteFuzzySet` instances.

        The natural join combines elements from the two sets based on common 
        attributes of their schemas, and applies `FuzzyLogic.and_fun()`
        operation to their membership values.
        The schema of the resulting fuzzy set is composed by the schema
        of the current fuzzy set concatenated with the remaining not in common
        attributes in the schema of 'set2'.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
                join with.

        Raises:
            AssertionError: If `set2` is not an instance of
                `DiscreteFuzzySet` or If `self` and `set2`
                do not have at least one attribute in common
                in their schemas.

        Returns:
            DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
                natural join of `self` and `set2`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('a', 'b'), {('x', 'y'): 0.8, ('x', 'z'): 0.5})
            >>> set_b = DiscreteFuzzySet(('b', 'c'), {('y', 'w'): 0.7, ('z', 'v'): 0.6})
            >>> result = set_a @ set_b
            >>> print(result.to_dictionary())
            {('x', 'y', 'w'): 0.7, ('x', 'z', 'v'): 0.5}

            In this example, the natural join of `set_a` and `set_b` is computed by combining elements
            based on the common attribute 'b'. The resulting set contains tuples from the combined schemas
            ('a', 'b', 'c') with membership values computed using the `FuzzyAnd.MIN`,
            set as the default `AND` truth function in the class `FuzzyLogic`.
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"

        schema1 = self.get_schema()
        schema2 = set2.get_schema()
        schema2_rem = list(schema2)
        indexes_to_check_1 = []
        indexes_to_check_2 = []
        indexes_to_insert_2 = list(range(0, len(schema2)))
        for index, var in enumerate(schema1):
            if var in schema2:
                indexes_to_check_1.append(index)
                indexes_to_check_2.append(schema2.index(var))
                indexes_to_insert_2.remove(schema2.index(var))
                schema2_rem.remove(var)
        assert len(indexes_to_check_1) > 0, "'set1' and 'set2' must have at least one set in common in their schema!"

        new_mf = {}
        for element1, membership1 in self.__mf.items():
            for element2, membership2 in set2.to_dictionary().items():
                to_insert = True
                for index in range(0, len(indexes_to_check_1)):
                    if element1[indexes_to_check_1[index]] != element2[indexes_to_check_2[index]]:
                        to_insert = False
                        break
                if to_insert:
                    new_elem = list(element1)
                    for index in indexes_to_insert_2:
                        new_elem.append(element2[index])
                    new_membership = FuzzyLogic.and_fun(membership1, membership2)
                    if new_membership > .0:
                        new_mf[tuple(new_elem)] = new_membership
        return DiscreteFuzzySet(schema1 + tuple(schema2_rem), new_mf)

    # NON TESTATO : Banale
    def __truediv__(self, set2) -> float: # proportion: /
        """Computes the proportion of elements in the intersection of `self` and `set2`
        relative to the total number of elements in `set2`.

        Args:
            set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
                divide by.

        Raises:
            AssertionError: If `set2` is not an instance of
                `DiscreteFuzzySet`, if the cardinality of `set2` is zero
                or if the assumprions of the fuzzy union are breached.

        Returns:
            float: The proportion of elements in the intersection of
                `self` and `set2` relative to `set2`.

        Examples:
            >>> set_a = DiscreteFuzzySet(('X', ), {('a', ): 0.7, ('b', ): 0.3, ('c', ): 0.5})
            >>> set_b = DiscreteFuzzySet(('X', ), {('b', ): 0.4, ('c', ): 0.5, ('d', ): 0.6})
            >>> proportion = set_a / set_b
            >>> print(proportion)
            0.5333333333333333

            In this example, the intersection of `set_a` and `set_b` contains the elements
            {('b', ), ('c', )} with a combined cardinality of 2, while `set_b` has a cardinality of 3.
            Therefore, the proportion returned is 2/3.
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
        assert set2.cardinality() > 0, "'set2' must have cardinality greater than 0!" 
        new_set = self & set2
        return new_set.cardinality() / set2.cardinality()

    def projection(self, subschema: Tuple[str], operator: FuzzyBinaryOperator) -> FuzzySet:
        """Projects the fuzzy set onto a specified subschema using a binary operator.

        This method creates a new fuzzy set by projecting the current set onto a given subschema. The
        projection involves combining membership values according to the provided binary operator. Only the
        elements in the new subschema are retained, and membership values are computed using the operator
        applied to the corresponding elements from the original fuzzy set.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            subschema (Tuple[str]): A tuple representing the subschema
                to project onto. It must be a non-empty subset of the
                schema of the current fuzzy set.
            operator (FuzzyBinaryOperator): A binary operator used to
                combine membership values during the projection.

        Raises:
            AssertionError: If `subschema` is not a non-empty tuple or
                if any variable in `subschema` is not in the schema of
                the fuzzy set, or if `operator` is not of type
                `FuzzyBinaryOperator`.

        Returns:
            FuzzySet: A new `DiscreteFuzzySet` object representing the
                projection of the original fuzzy set onto the specified
                subschema.

        Examples:
            >>> original_set = DiscreteFuzzySet(('x', 'y', 'z'), {('a', 'b', 'c'): 0.7, ('a', 'b', 'f'): 0.5, ('a', 'f', 'c'): 0.3})
            >>> subschema = ('x', 'y')
            >>> projected_set = original_set.projection(subschema, FuzzyAnd.MIN)
            >>> print(projected_set.to_dictionary())
            {('a', 'b'): 0.5, ('a', 'f'): .0.3}
        """
        assert isinstance(subschema, tuple) and len(subschema) > 0, "'subschema' must be a non empty sub-tuple of sets from the schema of the fuzzy set!"
        assert isinstance(operator, FuzzyBinaryOperator), "'operator' must be of type 'FuzzyBinaryOperator'!"

        if subschema == ('mu', ):
            data = set(self.__mf.values())
            df = DataFrame(data=data, columns=['memberships'])
            return DiscreteFuzzySet(mf=df)

        schema = self.get_schema()
        indexes = set()
        for var in subschema:
            assert var in schema or var == 'mu', "'" + str(var) + "' not in the schema of the fuzzy set!"
            indexes.add(schema.index(var))

        new_mf = {}
        to_remove = set()
        for element, membership in self.__mf.items():
            new_tuple = []
            for index in indexes:
                new_tuple.append(element[index])

            new_tuple = tuple(new_tuple)
            if new_tuple in new_mf.keys():
                new_mf[new_tuple] = operator(new_mf[new_tuple], membership)
            else:
                new_mf[new_tuple] = membership

            if new_mf[new_tuple] == .0:
                to_remove.add(new_tuple)

        for t in to_remove:
            del new_mf[t]

        fs = DiscreteFuzzySet(subschema, new_mf)
        return fs

    def particularization(self, assignment: dict) -> FuzzySet:
        """Performs particularization of the fuzzy set based on a given assignment.

        This method creates a new fuzzy set by specializing the current set according to the provided
        assignment. The assignment is a dictionary where each key is an attribute of the schema, and each
        value specifies a particular value or a fuzzy set. The resulting fuzzy set includes only the elements
        that match the specified assignments. If a variable in the assignment maps to a specific value,
        only elements with that value are retained. If it maps to another fuzzy set, the membership values
        are combined using fuzzy AND operations.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            assignment (dict): A dictionary where each key is an attribute
                from the schema of the fuzzy set, and each value is
                either a specific value or a `DiscreteFuzzySet` to match
                against.

        Raises:
            AssertionError: If `assignment` is not a dictionary, if any
                key is not in the fuzzy set's schema, or if the
                assignment values do not match the expected types.

        Returns:
            FuzzySet: A new `DiscreteFuzzySet` object that represents
                the particularized fuzzy set.

        Examples:
            >>> schema = ('x', 'y')
            >>> fuzzy_set = DiscreteFuzzySet(schema, {('a', 'b'): 0.5, ('c', 'd'): 0.7})
            >>> assignment = {'x': 'a', 'y': DiscreteFuzzySet(('y',), {('b',): 0.3})}
            >>> particularized_set = fuzzy_set.particularization(assignment)
            >>> print(particularized_set.to_dictionary())
            {('a', 'b'): 0.3}
        """
        assert isinstance(assignment, dict), "'assignment' must be a dictionary!"

        schema = self.get_schema()
        fs_assignments = []
        val_indexes = []
        fs_indexes = []
        for key in assignment:
            # assert isinstance(attr_tuple, tuple), "All keys in 'assignment' must be tuples of attributes in the schema!"
            if isinstance(key, tuple):
                indexes = []
                for attr in key:
                    assert attr in schema, "'" + str(attr) + "' not in the schema of the fuzzy set!"
                    indexes.append(schema.index(attr))
                assert isinstance(assignment[key], FuzzySet), "If a key in 'assignment' is a tuple, its value must be a FuzzySet!"
                fs_indexes.append(indexes)
                fs_assignments.append(key)
            else:
                assert key in schema, "'" + str(key) + "' not in the schema of the fuzzy set!"
                assert not isinstance(assignment[key], FuzzySet), "If a key in 'assignment' is an attribute name, the value must not be a FuzzySet!"
                val_indexes.append(schema.index(key))

        new_mf = {}
        for element, membership in self.__mf.items():
            for index in val_indexes:
                if element[index] != assignment[schema[index]]:
                    membership = .0
                    break
            for i in range(len(fs_assignments)):
                subtuple = ()
                for index in fs_indexes[i]:
                    subtuple += (element[index], )
                membership = FuzzyLogic.and_fun(assignment[fs_assignments[i]][subtuple], membership)
            if membership > .0:
                new_mf[element] = membership

        fs = DiscreteFuzzySet(schema, new_mf)
        return fs

    def cardinality(self) -> float:
        """Computes the cardinality of the fuzzy set.

        This method calculates the total sum of all membership values in the fuzzy set. The cardinality
        represents the aggregate degree of membership of all elements in the set.

        Returns:
            float: The sum of the membership values of all elements in
                the fuzzy set.

        Examples:
            >>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.4, ('c', 'd'): 0.6})
            >>> total_cardinality = fuzzy_set.cardinality()
            >>> print(total_cardinality)
            1.0
        """
        memberships_sum = 0.0
        for value in self.__mf.values():
            memberships_sum += value
        return memberships_sum

    def mean_cardinality(self) -> float:
        """Calculates the mean cardinality of the fuzzy set.

        This method computes the average membership value of all elements in the fuzzy set. The mean
        cardinality provides an indication of the average degree of membership of the elements in the set.

        Returns:
            float: The mean membership value of the fuzzy set. If the
                set is empty, the method returns 0.0.

        Examples:
            >>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.4, ('c', 'd'): 0.6})
            >>> mean_cardinality = fuzzy_set.mean_cardinality()
            >>> print(mean_cardinality)
            0.5
        """
        memberships_sum = 0.0
        n = 0
        for value in self.__mf.values():
            memberships_sum += value
            n += 1
        if n:
            return memberships_sum / n
        else:
            return .0

    def compatibility(self, set2: FuzzySet) -> FuzzySet:
        """Computes the compatibility of the current fuzzy set with another fuzzy set.

        This method calculates a new fuzzy set based on the membership values of `set2`,
        with each membership value in the new set representing the maximum compatibility
        with the corresponding membership value in the current set.
        Both fuzzy sets must have the same schema for the operation to be valid.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            set2 (FuzzySet): Another fuzzy set to compare with.
                It must have the same schema as the current fuzzy set.

        Raises:
            AssertionError: If `set2` is not of type `DiscreteFuzzySet`
                or if the schemas of the two fuzzy sets do not match.

        Returns:
            FuzzySet: A new `DiscreteFuzzySet` object where each element
                is associated with the maximum membership value between the
                two fuzzy sets.

        Examples:
            >>> fuzzy_set1 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.3, ('d', 'e'): 0.6, ('c', 'd'): 0.8, ('h', 'i'): 0.4})
            >>> fuzzy_set2 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('d', 'e'): 0.5, ('e', 'f'): 0.7, ('c', 'd'): 0.2})
            >>> compatible_set = fuzzy_set1.compatibility(fuzzy_set2)
            >>> print(compatible_set.to_dictionary())
            {(0.5, ): 0.6, (0.2, ): 0.8}
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
        assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

        new_mf = {}
        for element, membership in set2.to_dictionary().items():
            key = (membership, )
            if key in new_mf.keys():
                new_mf[key] = max(self[element], new_mf[key])
            elif self[element] > .0:
                new_mf[key] = self[element]

        return DiscreteFuzzySet(('membership', ), new_mf)

    def consistency(self, reference_set: FuzzySet) -> float:
        """Computes the consistency level between the fuzzy set and a reference fuzzy set.

        This method calculates the consistency of the current fuzzy set with a provided reference set.
        Consistency is defined as the maximum of the fuzzy AND operation applied to the membership
        values of corresponding elements in both sets. The two fuzzy sets must have the same schema
        for this operation to be valid.

        Args:
            reference_set (FuzzySet): A reference fuzzy set to compare
                with. It must have the same schema as the current fuzzy
                set.

        Raises:
            AssertionError: If `reference_set` is not of type
                `DiscreteFuzzySet` or if the schemas of the two fuzzy
                sets do not match.

        Returns:
            float: The consistency value, representing the maximum
                consistency level between the two sets.

        Examples:
            >>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.7, ('c', 'd'): 0.4})
            >>> reference_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.6, ('c', 'd'): 0.5})
            >>> consistency_value = fuzzy_set.consistency(reference_set)
            >>> print(consistency_value)
            0.6
        """
        assert isinstance(reference_set, DiscreteFuzzySet), "'reference_set' must be of type 'DiscreteFuzzySet'!"
        assert self.get_schema() == reference_set.get_schema(), "'reference_set' must have the same schema!"

        consistency = .0
        for element, mu1 in reference_set.to_dictionary().items():
            consistency = max(FuzzyLogic.and_fun(mu1, self[element]), consistency)

        return consistency

    def get_schema(self) -> Tuple[str]: # NON TESTATO : Banale
        """Retrieves the schema of the fuzzy set.

        This method returns the schema of the fuzzy set as a tuple of strings. The schema consists
        of the attribute names, which are associated with the sets that define the fuzzy set.

        Returns:
            (Tuple[str]): A tuple representing the schema of the fuzzy set.

        Examples:
            >>> fuzzy_set = DiscreteFuzzySet(('x', 'y', 'z'), {('a', 'b', 'c'): 0.5})
            >>> schema = fuzzy_set.get_schema()
            >>> print(schema)
            ('x', 'y', 'z').
        """
        return tuple(self.__schema)

    def rename_schema(self, ren_dict: dict) -> None: # NON TESTATO : Banale
        """Renames elements in the schema of the fuzzy set based on a provided dictionary.

        This method updates the schema of the fuzzy set by renaming its attributes according to the
        mappings provided in `ren_dict`. Each key in `ren_dict` corresponds to an existing attribute
        in the schema, and the associated value is the new name for that attribute. The method performs
        in-place modification of the schema.

        Args:
            ren_dict (dict): A dictionary where each key is an existing
                attribute in the schema and each value is the new name for
                that attribute.

        Raises:
            AssertionError: If `ren_dict` is not a dictionary, if any
                key or value in `ren_dict` is not a string, if any key
                is not found in the current schema, or if any value
                already exists in the current schema.

        Examples:
            >>> original_schema = ('x', 'y', 'z')
            >>> renaming_dict = {'x': 'a', 'y': 'b'}
            >>> fuzzy_set = DiscreteFuzzySet(original_schema, {('x_val', 'y_val', 'z_val'): 0.7})
            >>> fuzzy_set.rename_schema(renaming_dict)
            >>> schema = fuzzy_set.get_schema()
            >>> print(schema)
            ('a', 'b', 'z')
        """
        assert isinstance(ren_dict, dict), "'ren_dict' must be a dictionary!"
        for key, value in ren_dict.items():
            assert isinstance(key, str) and isinstance(key, str), "All keys and values in 'ren_dict' must be strings!"
            assert key != 'mu', "'mu' cannot be a valid attribute name in the schema!"
            assert key in self.__schema, "'" + key + "' not in this FuzzySet schema!"
            assert value not in self.__schema, "'" + value + "' already in this FuzzySet schema!"
            self.__schema[self.__schema.index(key)] = value

    def select(self, condition: Callable) -> FuzzySet: # NON TESTATO : Banale
        """Selects elements from the fuzzy set based on a condition.

        This method filters the fuzzy set by applying a specified condition to each element and its
        membership value. The condition is a callable that takes a tuple consisting of the element and
        its membership value, and returns a boolean indicating whether the element should be included
        in the new fuzzy set. The resulting fuzzy set contains only the elements that satisfy the condition.

        Args:
            condition (Callable): A callable function that takes a tuple
                (element, membership) and returns `True` if the element
                should be included in the resulting set, `False`
                otherwise.

        Raises:
            AssertionError: If `condition` is not a callable function.

        Returns:
            FuzzySet: A new `DiscreteFuzzySet` object containing only
                the elements that satisfy the condition.

        Examples:
            >>> condition = lambda x: x[-1] > 0.6
            >>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8})
            >>> selected_set = original_set.select(condition)
            >>> print(selected_set)
            {('c', 'd'): 0.8}
        """
        assert isinstance(condition, Callable), "'function' must be a callable function!"
        new_mf = {}
        for element, membership in self.__mf.items():
            if condition(tuple(list(element) + [membership])):
                new_mf[element] = membership
        return DiscreteFuzzySet(self.get_schema(), new_mf)

    def apply(self, operator: FuzzyUnaryOperator) -> FuzzySet:
        """Applies a fuzzy unary operator to all membership values in the fuzzy set.

        This method applies a specified 'FuzzyUnaryOperator' to each membership value in the fuzzy set,
        producing a new fuzzy set with the modified membership values.

        >> **WARNING**: only tuples with a membership value greater than 0 are kept.

        Args:
            operator (FuzzyUnaryOperator): A unary operator that
                operates on the membership values of the fuzzy set.

        Raises:
            AssertionError: If `operator` is not of type
                `FuzzyUnaryOperator`.

        Returns:
            FuzzySet: A new `DiscreteFuzzySet` object where the
                membership values have been modified according to the given
                unary operator.

        Examples:
            >>> operator = LinguisticModifiers.VERY
            >>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8})
            >>> modified_set = original_set.apply(operator)
            >>> print(modified_set.to_dictionary())
            {('a', 'b'): 0.25, ('c', 'd'): 0.64}
        """
        assert isinstance(operator, FuzzyUnaryOperator), "'operator' must be of type 'FuzzyUnaryOperator'!"
        new_mf = {}
        for element, membership in self.__mf.items():
            new_membership = operator(membership)
            if new_membership > .0:
                new_mf[element] = new_membership
        return DiscreteFuzzySet(self.get_schema(), new_mf)

    def extension_principle(self, function: Callable, out_schema: tuple) -> FuzzySet: # NON TESTATO
        """Computes the fuzzy extension of the function defined on the tuples of this fuzzy set.

        This method applies a specified function to each element in the fuzzy set, mapping it to a new
        schema (`out_schema`). The resulting fuzzy set consists of the function's output as the new
        elements and their associated membership values. If multiple elements map to the same output,
        their membership values are combined using the fuzzy OR operation.

        Args:
            function (Callable): A callable function that maps
            out_schema (tuple): The schema of the resulting fuzzy
                elements from the original schema to the output schema.
                set after applying the function.

        Raises:
            AssertionError: If 'function' is not a callable.

        Returns:
            FuzzySet: A new 'DiscreteFuzzySet' object representing
                the fuzzy extension of the original given function.

        Examples:
            >>> def example_function(element):
            ...     if element == ('a', 'b'):
            ...         return ('c', )
            ...     elif element == ('c', 'd'):
            ...         return ('c', )
            ...     elif element == ('e', 'f'):
            ...         return ('d', )

            >>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8, ('e', 'f'): 0.4})
            >>> out_schema = ('z',)
            >>> fuzzy_fun = original_set.extension_principle(example_function, out_schema)
            >>> print(fuzzy_fun.to_dictionary())
            {('c', ): 0.8, ('d', ): 0.4}
            >>> print(fuzzy_fun.get_schema())
            ('z',)
        """
        assert isinstance(function, Callable), "'function' must be a callable function!"
        new_mf = {}
        for element, membership in self.__mf.items():
            y = function(element)
            if y in new_mf:
                new_mf[y] = FuzzyLogic.or_fun(new_mf[y], membership)
            else:
                new_mf[y] = membership
        return DiscreteFuzzySet(out_schema, new_mf)

    def cylindrical_extension(self, set2: FuzzySet) -> Tuple[FuzzySet, FuzzySet]:
        """Computes the cylindrical extension of two fuzzy sets over a combined schema.

        This method extends the current fuzzy set and `set2` to a common schema
        that includes all attributes from both schemas. The resulting sets have the same schema, with
        membership values adjusted according to the cylindrical extension. The new schema is created by
        merging the attributes of the two schemas:\n
        - First the common attributes are added in the same order as their appear in the `self` schema
        - Second the unique attributes of the `self` schema are concatenated in the same order as their appear in it
        - Third the unique attributes of the `set2` schema are concatenated in the same order as their appear in it

        Args:
            set2 (DiscreteFuzzySet): Another fuzzy set to be extended.
                It must be an instance of `DiscreteFuzzySet`.

        Raises:
            AssertionError: If `set2` is not of type `DiscreteFuzzySet`.

        Returns:
            (Tuple[FuzzySet, FuzzySet]): A tuple containing two
                `DiscreteFuzzySet` objects \n
                - The first is the cylindrical
                extension of the original set.
                - The second is the
                cylindrical extension of `set2`.

        Examples:
            >>> set1 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.4})
            >>> set2 = DiscreteFuzzySet(('y', 'z'), {('e', 'f'): 0.7, ('g', 'h'): 0.3})
            >>> extended_set1, extended_set2 = set1.cylindrical_extension(set2)
            >>> print(extended_set1.get_schema())
            ('y', 'x', 'z')
            >>> print(extended_set2.get_schema())
            ('y', 'x', 'z')
            >>> print(extended_set1.to_dictionary())
            {('b', 'a', 'f'): 0.5, ('b', 'a', 'h'): 0.5, ('d', 'c', 'f'): 0.4, ('d', 'c', 'h'): 0.4}
            >>> print(extended_set2.to_dictionary())
            {('e', 'a', 'f'): 0.7, ('e', 'c', 'f'): 0.7, ('g', 'a', 'h'): 0.3, ('g', 'c', 'h'): 0.3}
        """
        assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'"
        schema1 = self.get_schema()
        schema2 = set2.get_schema()
        to_insert_1 = list(range(len(schema1)))
        to_insert_2 = list(range(len(schema2)))
        common1 = []
        common2 = []
        for index, var in enumerate(schema1):
            if var in schema2:
                common1.append(index)
                to_insert_1.remove(index)
                index2 = schema2.index(var)
                to_insert_2.remove(index2)
                common2.append(index2)

        new_schema = []
        for index in common1:
            new_schema.append(schema1[index])
        for index in to_insert_1:
            new_schema.append(schema1[index])
        for index in to_insert_2:
            new_schema.append(schema2[index])
        new_schema = tuple(new_schema)

        mf1_ext = {}
        mf2_ext = {}
        for element1, membership1 in self.__mf.items():
            for element2, membership2 in set2.to_dictionary().items():

                new_elem = []
                for index in common1:
                    new_elem.append(element1[index])
                for index in to_insert_1:
                    new_elem.append(element1[index])
                for index in to_insert_2:
                    new_elem.append(element2[index])
                mf1_ext[tuple(new_elem)] = membership1

                new_elem = []
                for index in common2:
                    new_elem.append(element2[index])
                for index in to_insert_1:
                    new_elem.append(element1[index])
                for index in to_insert_2:
                    new_elem.append(element2[index])
                mf2_ext[tuple(new_elem)] = membership2

        return DiscreteFuzzySet(new_schema, mf1_ext), DiscreteFuzzySet(new_schema, mf2_ext)

    def collapse(self, operator: FuzzyBinaryOperator) -> float: # differentia # NON TESTATO : Banale
        """Collapses the fuzzy set into a single membership value using a specified binary operator.

        This method reduces the fuzzy set to a single membership value by applying a binary operator
        iteratively across all membership values in the fuzzy set. The operator must be an instance
        of `FuzzyBinaryOperator`, and the fuzzy set must contain at least two elements.

        Args:
            operator (FuzzyBinaryOperator): A binary operator used to
                collapse the fuzzy set.

        Raises:
            AssertionError: If 'operator' is not of type
                `FuzzyBinaryOperator` or if the fuzzy set contains fewer
                than two elements.

        Returns:
            float: The resulting membership value after applying the
                binary operator across all elements.

        Examples:
            >>> operator = FuzzyAnd.MIN  # Example operator: minimum of two values
            >>> fuzzy_set = DiscreteFuzzySet(schema, {('a', 'b'): 0.4, ('c', 'd'): 0.6})
            >>> collapsed_value = fuzzy_set.collapse(operator)
            >>> print(collapsed_value)
            0.4
        """
        assert isinstance(operator, FuzzyBinaryOperator), "'operator' must be of type 'FuzzyBinaryOperator'!"
        assert len(self.__mf.keys()) > 1, "The fuzzy set must have at least two elements!"
        memberships = list(self.__mf.values())
        if len(memberships) > 0:
            result = memberships[0]
            for membership in memberships[1:]:
                result = operator(result, membership)
        else:
            result = .0
        return result

    def reorder(self, new_schema: tuple) -> FuzzySet:
        """Reorders the elements of the fuzzy set according to a new schema permutation.

        This method takes a new schema represented by a tuple, which should be a permutation of the current schema,
        and reorders the elements of the fuzzy set to match the order of the attributes in the new schema.
        The method returns a new `FuzzySet` object with the updated schema and reordered elements.

        Args:
            new_schema (tuple): A tuple representing a permutation of
                the original schema. The length of `new_schema` must be
                the same as the original schema.

        Raises:
            AssertionError: If `new_schema` is not a tuple, if its
                length differs from the original schema, or if any
                element in `new_schema` is not present in the original
                schema.

        Returns:
            FuzzySet: A new `FuzzySet` object with the reordered schema
                and elements.

        Examples:
            >>> original_schema = ('x', 'y', 'z')
            >>> new_schema = ('z', 'x', 'y')
            >>> fuzzy_set = DiscreteFuzzySet(original_schema, {('a', 'b', 'c'): 0.5, ('d', 'e', 'f'): 0.2})
            >>> reordered_set = fuzzy_set.reorder(new_schema)
            >>> print(reordered_set.to_dictionary())
            {('c', 'a', 'b'): 0.5, ('f', 'd', 'e'): 0.2}
        """
        assert isinstance(new_schema, tuple), "'tuple' must be a tuple representing a permutation of the schema!"
        assert len(new_schema) == len(self.__schema), "'tuple' must be of the same length as the original schema!"
        perm = []
        for var in new_schema:
            assert var in self.__schema, "'" + str(var) + "' not in the schema!"
            index = self.__schema.index(var)
            perm.append(index)

        new_dict = {}
        for key, value in self.__mf.items():
            new_elem = []
            for i in perm:
                new_elem.append(key[i])
            new_dict[tuple(new_elem)] = value

        return DiscreteFuzzySet(new_schema, new_dict)

    def probability(self, prob: Callable) -> float:
        p = .0
        for element, mu in self.__mf.items():
            p += mu * prob(element)
        return p

    def truth(self, truth: Callable) -> FuzzySet:
        new_mf = {}
        for element, mu in self.__mf.items():
            new_mu = truth(self.__mf[element])
            if new_mu > .0:
                new_mf[element] = new_mu
        return DiscreteFuzzySet(self.get_schema(), new_mf)

    def to_dictionary(self) -> dict: # differentia
        """Returns a dictionary where in the (key, value) pair
        the key represents the element in the fuzzy relation
        and the value represents its membership degree.

        Returns:
            dict: A dictionary representing the fuzzy relation.
        """
        return self.__mf.copy()

    def elements(self) -> set: # differentia
        """Returns the set of all tuples in this fuzzy relation.

        Returns:
            set: The set of all tuples in this fuzzy relation.
        """
        return set(self.__mf.keys())

    def memberships(self) -> set: # differentia
        """Returns the set of all memberships in this fuzzy relation.

        Returns:
            set: The set of all memberships in this fuzzy relation.
        """
        return set(self.__mf.values())

    def items(self) -> set: # differentia
        """Returns the set of all (tuple, memberships) pairs
        in this fuzzy relation.

        Returns:
            set: The set of all (tuple, memberships) pairs
                in this fuzzy relation.
        """
        return set(self.__mf.items())

    def tab_str(self, n_cols = 1, max_rows=10, padding=4) -> str: # differentia
        """Returns the tabular format string of this
        fuzzy relation.

        Returns:
            str: The tabular format string of this
                fuzzy relation.
        """
        max_rows = min(len(self.__mf), max_rows)
        assert n_cols <= ceil(len(self.__mf) / max_rows), "'n_cols' exceeds the minimum number of columns necessary to describe this fuzzy set!"
        count = 0
        s = ''
        table = [()] * max_rows
        header = []
        cols = len(self.__schema) + 1
        for c in range(n_cols):
            header += self.__schema + ['mu']

        is_truncated = False
        for key, value in self.__mf.items():
            if count < max_rows * n_cols:
                table[count % max_rows] += key + (round(value, 2), )
                count += 1
            else:
                is_truncated = True
                break

        while count < max_rows * n_cols:
            table[count % max_rows] += ('/',) * cols
            count += 1

        col_widths = [max(len(str(item)) for item in col) + padding for col in zip(header, *table)]

        s = "".join(f"{attr:<{col_widths[i]}}" for i, attr in enumerate(header)) + "\n"
        for row in table:
            s += "".join(f"{str(cell):<{col_widths[i]}}" for i, cell in enumerate(row)) + "\n"
        if is_truncated:
            s += 'Output truncated: n. of rows = ' + str(len(self.__mf))
        return s

    # def comparison_str(self, set2, n_cols = 1, max_rows=10, padding=4) -> str: # differentia
    #     """Returns the tabular format string of this
    #     fuzzy relation.

    #     Returns:
    #         str: The tabular format string of this
    #             fuzzy relation.
    #     """
    #     max_rows = min(len(self.__mf), max_rows)
    #     assert n_cols <= ceil(len(self.__mf) / max_rows), "'n_cols' exceeds the minimum number of columns necessary to describe this fuzzy set!"
    #     count = 0
    #     s = ''
    #     table = [()] * max_rows
    #     header = []
    #     cols = len(self.__schema) + 2
    #     for c in range(n_cols):
    #         header += self.__schema + ['mu', '-> new_mu']

    #     is_truncated = False
    #     for key, value in self.__mf.items():
    #         if count < max_rows * n_cols:
    #             set2_value = set2[key]
    #             table[count % max_rows] += key + (round(value, 2), '-> ' + str(round(set2_value, 2)))
    #             count += 1
    #         else:
    #             is_truncated = True
    #             break

    #     while count < max_rows * n_cols:
    #         table[count % max_rows] += ('/',) * cols
    #         count += 1

    #     col_widths = [max(len(str(item)) for item in col) + padding for col in zip(header, *table)]

    #     s = "".join(f"{attr:<{col_widths[i]}}" for i, attr in enumerate(header)) + "\n"
    #     for row in table:
    #         s += "".join(f"{str(cell):<{col_widths[i]}}" for i, cell in enumerate(row)) + "\n"
    #     if is_truncated:
    #         s += 'Output truncated: n. of rows = ' + str(len(self.__mf))
    #     return s

    def comparison_str(self, set2: DiscreteFuzzySet) -> str:
        max_rows = 10
        count = 0
        s = ''
        table = []
        padding = 4
        header = self.__schema + ['mu', '-> new_mu']

        for key, value in self.__mf.items():
            set2_value = set2[key]
            if count < max_rows:
                if self.__mf[key] != set2_value:
                    table.append(key + (str(round(value, 2)), '-> ' + str(round(set2_value, 2)), ))            
                    count += 1
            else:
                break

        col_widths = [max(len(str(item)) for item in col) + padding for col in zip(header, *table)]

        s = "".join(f"{header:<{col_widths[i]}}" for i, header in enumerate(header)) + "\n"
        for row in table:
            s += "".join(f"{str(cell):<{col_widths[i]}}" for i, cell in enumerate(row)) + "\n"
        if count >= max_rows:
            s += 'Output truncated: n. of rows = ' + str(len(self.__mf))
        return s

    def __repr__(self) -> str: # differentia
        """Returns the Zadeh inline string representation of this
        fuzzy set.

        Returns:
            str: Returns the Zadeh inline string
                representation of this fuzzy set.
        """
        s = ''
        for value, membership in self.__mf.items():
            if membership > .0:
                s += str(membership) + '/' + str(value) + ' + '
        if len(self.__mf.items()) > 0:
            return s[:-3]
        return '∅'

    def __str__(self) -> str: # differentia
        """Returns string representation of this
        fuzzy set.

        Returns:
            str: Returns the string representation of this
                fuzzy set.
        """
        return self.__repr__()

__and__(set2)

Computes the intersection of two FuzzySet instances.

The intersection is calculated by applying FuzzyLogic.and_fun() function to the membership values of corresponding elements in both sets.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
  • set2 (FuzzySet) –

    The FuzzySet instance to intersect with self.

Raises:
  • AssertionError

    If set2 is not an instance of FuzzySet or if self and set2 do not have the same schema.

Returns:
  • DiscreteFuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet representing the intersection of self and set2.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5, ('z', ): .3})
>>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
>>> result = set_a & set_b
>>> print(result.to_dictionary())
{('x',): 0.4, ('y',): 0.5}

In this example, the intersection of set_a and set_b is computed by applying the FuzzyAnd.MIN function, set as the default AND truth function of the class FuzzyLogic, to the membership values of corresponding elements. The resulting set contains elements with membership values that represent the minimum of the corresponding values in set_a and set_b.

Source code in pyPRUF/fuzzy_sets.py
def __and__(self, set2: FuzzySet) -> FuzzySet: # intersection: &, &=
    """Computes the intersection of two `FuzzySet` instances.

    The intersection is calculated by applying `FuzzyLogic.and_fun()`
    function to the membership values of corresponding elements in both sets.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        set2 (FuzzySet): The `FuzzySet` instance to intersect with
            `self`.

    Raises:
        AssertionError: If `set2` is not an instance of `FuzzySet`
            or if `self` and `set2` do not have the same schema.

    Returns:
        DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
            intersection of `self` and `set2`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5, ('z', ): .3})
        >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
        >>> result = set_a & set_b
        >>> print(result.to_dictionary())
        {('x',): 0.4, ('y',): 0.5}

        In this example, the intersection of `set_a` and `set_b` is computed by applying
        the `FuzzyAnd.MIN` function, set as the default `AND` truth function of the class `FuzzyLogic`,
        to the membership values of corresponding elements. The resulting set contains elements
        with membership values that represent the minimum of the
        corresponding values in `set_a` and `set_b`.
    """
    assert isinstance(set2, FuzzySet), "'set2' must be of type 'FuzzySet'!"
    assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

    new_mf = {}
    for element, membership1 in self.__mf.items():
        new_membership = FuzzyLogic.and_fun(membership1, set2[element])
        if new_membership > .0:
            new_mf[element] = new_membership
    fs = DiscreteFuzzySet(self.get_schema(), new_mf)
    return fs

__delitem__(element)

Deletes an element from the DiscreteFuzzySet.

If the element exists in the set, it is deleted; otherwise, the operation has no effect.

Parameters:
  • element (tuple) –

    The element to remove from the DiscreteFuzzySet.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
>>> del set_a[('x',)]
>>> print(set_a.to_dictionary())
{('y',): 0.5}

In this example, the element ('x',) is deleted from set_a. After deletion, the resulting set only contains the remaining element ('y',).

Source code in pyPRUF/fuzzy_sets.py
def __delitem__(self, element) -> None:
    """Deletes an element from the `DiscreteFuzzySet`.

    If the `element` exists in the set, it is deleted;
    otherwise, the operation has no effect.

    Args:
        element (tuple): The element to remove from the
            `DiscreteFuzzySet`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
        >>> del set_a[('x',)]
        >>> print(set_a.to_dictionary())
        {('y',): 0.5}

        In this example, the element `('x',)` is deleted from `set_a`. After deletion,
        the resulting set only contains the remaining element `('y',)`.
    """
    if element in self.__mf:
        del self.__mf[element]

__eq__(set2)

Determines whether two DiscreteFuzzySet instances are equal.

Two DiscreteFuzzySet instances are considered equal if they have the same schema and identical membership values for all elements.

Parameters:
  • set2 (DiscreteFuzzySet) –

    The DiscreteFuzzySet instance to compare with self.

Raises:
  • AssertionError

    If set2 is not an instance of DiscreteFuzzySet.

Returns:
  • bool( bool ) –

    True if self and set2 are identical fuzzy sets; False otherwise.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
>>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
>>> set_c = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
>>> set_d = DiscreteFuzzySet(('d',), {('x',): 0.7, ('y',): 0.5})
>>> result1 = (set_a == set_b)
>>> result2 = (set_a == set_c)
>>> result3 = (set_a == set_d)
>>> print(result1)
True
>>> print(result2)
False
>>> print(result3)
False

In this example, set_a is equal to set_b because they have the same schema and identical membership values for all elements. However, set_a is not equal to set_c due to differences in their membership values while set_a is not equal to set_d due to to differences in their schemas.

Source code in pyPRUF/fuzzy_sets.py
def __eq__(self, set2: FuzzySet) -> bool:
    """Determines whether two `DiscreteFuzzySet` instances are equal.

    Two `DiscreteFuzzySet` instances are considered equal if they have the same schema
    and identical membership values for all elements.

    Args:
        set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
            compare with `self`.

    Raises:
        AssertionError: If `set2` is not an instance of
            `DiscreteFuzzySet`.

    Returns:
        bool: `True` if `self` and `set2` are identical fuzzy sets;
            `False` otherwise.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
        >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
        >>> set_c = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
        >>> set_d = DiscreteFuzzySet(('d',), {('x',): 0.7, ('y',): 0.5})
        >>> result1 = (set_a == set_b)
        >>> result2 = (set_a == set_c)
        >>> result3 = (set_a == set_d)
        >>> print(result1)
        True
        >>> print(result2)
        False
        >>> print(result3)
        False

        In this example, `set_a` is equal to `set_b` because they have the same schema and identical
        membership values for all elements. However, `set_a` is not equal to `set_c` due to differences
        in their membership values while `set_a` is not equal to `set_d` due to to differences in
        their schemas.
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
    return set2.__mf == self.__mf and set2.get_schema() == self.get_schema()

__getitem__(element)

Retrieves the membership value of an element from the DiscreteFuzzySet.

If the element exists in the set, its membership value is returned; otherwise, a membership value of 0.0 is returned.

Parameters:
  • element (tuple) –

    The element for which to retrieve the membership value.

Returns:
  • float( float ) –

    The membership value of the element. Returns 0.0 if the element is not in the set.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
>>> membership_x = set_a[('x',)]
>>> membership_z = set_a[('z',)]
>>> membership_fake = set_a['fake']
>>> print(membership_x)
0.7
>>> print(membership_z)
0.0
>>> print(membership_fake)
0.0

In this example, the membership value for ('x',) is retrieved and found to be 0.7. For the element ('z',), which does not exist in the set, the method returns a membership value of 0.0.

Source code in pyPRUF/fuzzy_sets.py
def __getitem__(self, element) -> float: # membership: []
    """Retrieves the membership value of an element from the `DiscreteFuzzySet`.

    If the `element` exists in the set, its membership value is returned;
    otherwise, a membership value of 0.0 is returned.

    Args:
        element (tuple): The element for which to retrieve the
            membership value.

    Returns:
        float: The membership value of the `element`.
            Returns 0.0 if the `element` is not in the set.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
        >>> membership_x = set_a[('x',)]
        >>> membership_z = set_a[('z',)]
        >>> membership_fake = set_a['fake']
        >>> print(membership_x)
        0.7
        >>> print(membership_z)
        0.0
        >>> print(membership_fake)
        0.0

        In this example, the membership value for `('x',)` is retrieved and found to be 0.7.
        For the element `('z',)`, which does not exist in the set, the method returns a membership
        value of 0.0.
    """
    if element in self.__mf.keys():
        return self.__mf[element]
    return .0

__init__(schema=None, mf=None)

Constructs the DiscreteFuzzySet object with the schema and membership function (mf) given in input. This constructor can initialize the fuzzy set from either a pandas DataFrame or a dictionary.

Parameters:
  • schema (tuple, default: None ) –

    A non-empty tuple of strings without duplicates representing the schema of the DiscreteFuzzySet

  • mf (Union[DataFrame, dict], default: None ) –

    Can be None, a dict or a pandas DataFrame representing the tuples in the fuzzy relation. If mf is None, it will construct an empty fuzzy relation with the schema specified. If mf is a dict, it must have the tuples of the fuzzy relation as keys and their membership as the value of the (key, value) pair. Each key in the dict must be a tuple of the same length as the schema. If mf is a DataFrame, it must have

    • at least one column and at least one tuple
    • for each column a string representing the set name
    • a column named 'mu' containing the membership value of the corresponding tuple. If this column is missing, all tuples are assumed to belong to the fuzzy relation with membership degree 1. All memberships value in mf must be floats in the interval (0, 1].

If mf is a DataFrame, it is not required to also specify the schema parameter. If it is done anyway, it will be ignored and the schema will be inferred from the DataFrame columns names. If mf is a tuple of strings or None, it is required to specify also the schema.

Raises:
  • AssertionError

    If one of the preceding assumptions is breached.

Examples:

>>> A = DiscreteFuzzySet(('D1', 'D2'), {(1, 'val2'): 0.3, ('val1', 3.4): 0.6, (2, 'val2'): 0.9})
>>> print(A.to_dictionary())
{(1, 'val2'): 0.3, ('val1', 3.4): 0.6, (2, 'val2'): 0.9}
>>> print(A.get_schema())
('D1', 'D2')
>>> df = pd.DataFrame({
...     'x': [1, 2],
...     'y': [3, 4],
...     'mu': [0.5, 0.7]
... })
>>> fuzzy_set = FuzzySet(mf=df)
>>> print(fuzzy_set.get_schema())
('x', 'y')
>>> print(fuzzy_set.to_dictionary())
{(1, 3): 0.5, (2, 4): 0.7}
Source code in pyPRUF/fuzzy_sets.py
def __init__(self, schema: Tuple[str] = None, mf: Union[DataFrame, dict] = None):
    """Constructs the DiscreteFuzzySet object with the schema
    and membership function (mf) given in input. This constructor can initialize
    the fuzzy set from either a pandas DataFrame or a dictionary.

    Args:
        schema (tuple): A non-empty tuple of strings without
            duplicates representing the schema of the
            `DiscreteFuzzySet`
        mf: Can be `None`, a `dict` or a pandas `DataFrame`
            representing the tuples in the fuzzy relation. If
            mf is `None`, it will construct an empty fuzzy relation
            with the schema specified. If mf is a `dict`,
            it must have the tuples of the fuzzy relation as keys
            and their membership as the value of the (key, value)
            pair. Each key in the dict must be a tuple of the same
            length as the schema. If mf is a `DataFrame`, it must
            have\n
            - at least one column and at least one tuple
            - for each column a string representing the set name
            - a column named 'mu' containing the membership value of
            the corresponding tuple. If this column is missing, all
            tuples are assumed to belong to the fuzzy relation with
            membership degree 1. All memberships value in mf
            must be floats in the interval (0, 1].
    If mf is a DataFrame, it is not required to also specify the schema
    parameter. If it is done anyway, it will be ignored and the schema will
    be inferred from the DataFrame columns names. If mf is a tuple
    of strings or None, it is required to specify also the schema.

    Raises:
        AssertionError: If one of the preceding assumptions is
            breached.

    Examples:
        >>> A = DiscreteFuzzySet(('D1', 'D2'), {(1, 'val2'): 0.3, ('val1', 3.4): 0.6, (2, 'val2'): 0.9})
        >>> print(A.to_dictionary())
        {(1, 'val2'): 0.3, ('val1', 3.4): 0.6, (2, 'val2'): 0.9}
        >>> print(A.get_schema())
        ('D1', 'D2')

        >>> df = pd.DataFrame({
        ...     'x': [1, 2],
        ...     'y': [3, 4],
        ...     'mu': [0.5, 0.7]
        ... })
        >>> fuzzy_set = FuzzySet(mf=df)
        >>> print(fuzzy_set.get_schema())
        ('x', 'y')
        >>> print(fuzzy_set.to_dictionary())
        {(1, 3): 0.5, (2, 4): 0.7}
    """
    if isinstance(mf, DataFrame):
        dict_relation = {}
        for key, value in mf.to_dict().items():
            assert isinstance(key, str), "All keys in 'dict_relation' must be strings representing the variables name!"
            dict_relation[key] = tuple(value.values())
        keys = list(dict_relation.keys())
        assert len(keys) > 0, "There must be at least one column!"
        assert len(keys) != 1 or keys[0] != 'mu', "With the only column named 'mu' the resulting schema is empty!"
        length = len(dict_relation[keys[0]])
        assert length > 0, "The fuzzy set must contain at least a tuple!"

        self.__mf = {}
        for i in range(0, length):
            variables = []
            mu = 1.0
            for key in keys:
                if key != 'mu':
                    variables.append(dict_relation[key][i])
                else:
                    mu = dict_relation[key][i]
                    assert isinstance(mu, float) and 0.0 < mu and mu <= 1.0, "All values in 'mu' must be floats between (0, 1]!"
            self.__mf[tuple(variables)] = mu

        if 'mu' in keys:
            keys.remove('mu')
        self.__schema = keys
    else:
        assert isinstance(schema, tuple) and len(schema) > 0, "'schema' must be a non-empty tuple of strings representing the schemas name!"
        length = len(schema)
        sorted_schema = list(schema)
        sorted_schema.sort()
        for i in range(1, len(sorted_schema)):
            assert schema[i] != 'mu', "'mu' cannot be a valid attribute name in the schema!"
            assert schema[i] != schema[i - 1], "'schema' must not contains duplicates!"

        self.__mf = {}
        if mf is not None:
            assert isinstance(mf, dict), "'mf' must be 'None', a dictionary or a Dataframe!"
            for element, mu in mf.items():
                assert isinstance(element, tuple) and len(element) == length, "All keys in 'mf' must be tuples with the same length as the schema!"
                assert isinstance(mu, float) and 0.0 < mu and mu <= 1.0, "All memberships values must be floats between (0, 1]!"
                self.__mf[element] = mu

        self.__schema = list(schema)

__invert__()

Computes the current fuzzy set applied to the NOT FuzzyUnaryOperator.

The result is calculated by applying FuzzyLogic.not_fun() function to the membership values of all elements in the set.

WARNING: This operation corresponds neither to the set absolute complement nor to the relative complement. The set absolute/relative complement could be derived by calculating it as the difference between a fuzzy set (eventually the universe of discourse) and another fuzzy set. This correspond simply to the NOT FuzzyUnaryOperator applied to the tuples of this fuzzy set. Semantically corresponds to an application of a linguistic modifier.

WARNING: only tuples with a membership value greater than 0 are kept.

Returns:
  • DiscreteFuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet representing the fuzzy complement of self.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.4}, ('z', ): 1.0)
>>> result = ~set_a
>>> print(result.to_dictionary())
{('x',): 0.3, ('y',): 0.6}

In this example, the result is computed by applying the FuzzyNot.STANDARD function, set as the default NOT truth function of the class FuzzyLogic, to each membership value in the set. The resulting set contains only the elements with a positive membership value.

Source code in pyPRUF/fuzzy_sets.py
def __invert__(self) -> FuzzySet: # fuzzy_not unary operator: ~ # NON TESTATO : Banale
    """Computes the current fuzzy set applied to the NOT FuzzyUnaryOperator.

    The result is calculated by applying `FuzzyLogic.not_fun()`
    function to the membership values of all elements in the set.

    >> **WARNING**: This operation corresponds neither to the
    set absolute complement nor to the relative complement.
    The set absolute/relative complement could be derived
    by calculating it as the difference between a fuzzy set
    (eventually the universe of discourse) and another fuzzy set.
    This correspond simply to the NOT FuzzyUnaryOperator
    applied to the tuples of this fuzzy set. Semantically
    corresponds to an application of a linguistic modifier.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Returns:
        DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
            fuzzy complement of `self`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.4}, ('z', ): 1.0)
        >>> result = ~set_a
        >>> print(result.to_dictionary())
        {('x',): 0.3, ('y',): 0.6}

        In this example, the result is computed by applying the
        `FuzzyNot.STANDARD` function, set as the default `NOT` truth
        function of the class `FuzzyLogic`, to each membership value in the set.
        The resulting set contains only the elements with a positive membership value.
    """
    new_mf = {}
    for element, membership in self.__mf.items():
        new_mf[element] = FuzzyLogic.not_fun(membership)
    fs = DiscreteFuzzySet(self.get_schema(), new_mf)
    return fs

__matmul__(set2)

Computes the natural join of two DiscreteFuzzySet instances.

The natural join combines elements from the two sets based on common attributes of their schemas, and applies FuzzyLogic.and_fun() operation to their membership values. The schema of the resulting fuzzy set is composed by the schema of the current fuzzy set concatenated with the remaining not in common attributes in the schema of 'set2'.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
Raises:
  • AssertionError

    If set2 is not an instance of DiscreteFuzzySet or If self and set2 do not have at least one attribute in common in their schemas.

Returns:
  • DiscreteFuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet representing the natural join of self and set2.

Examples:

>>> set_a = DiscreteFuzzySet(('a', 'b'), {('x', 'y'): 0.8, ('x', 'z'): 0.5})
>>> set_b = DiscreteFuzzySet(('b', 'c'), {('y', 'w'): 0.7, ('z', 'v'): 0.6})
>>> result = set_a @ set_b
>>> print(result.to_dictionary())
{('x', 'y', 'w'): 0.7, ('x', 'z', 'v'): 0.5}

In this example, the natural join of set_a and set_b is computed by combining elements based on the common attribute 'b'. The resulting set contains tuples from the combined schemas ('a', 'b', 'c') with membership values computed using the FuzzyAnd.MIN, set as the default AND truth function in the class FuzzyLogic.

Source code in pyPRUF/fuzzy_sets.py
def __matmul__(self, set2: FuzzySet) -> FuzzySet: # natural_join: @, @=
    """Computes the natural join of two `DiscreteFuzzySet` instances.

    The natural join combines elements from the two sets based on common 
    attributes of their schemas, and applies `FuzzyLogic.and_fun()`
    operation to their membership values.
    The schema of the resulting fuzzy set is composed by the schema
    of the current fuzzy set concatenated with the remaining not in common
    attributes in the schema of 'set2'.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
            join with.

    Raises:
        AssertionError: If `set2` is not an instance of
            `DiscreteFuzzySet` or If `self` and `set2`
            do not have at least one attribute in common
            in their schemas.

    Returns:
        DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
            natural join of `self` and `set2`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a', 'b'), {('x', 'y'): 0.8, ('x', 'z'): 0.5})
        >>> set_b = DiscreteFuzzySet(('b', 'c'), {('y', 'w'): 0.7, ('z', 'v'): 0.6})
        >>> result = set_a @ set_b
        >>> print(result.to_dictionary())
        {('x', 'y', 'w'): 0.7, ('x', 'z', 'v'): 0.5}

        In this example, the natural join of `set_a` and `set_b` is computed by combining elements
        based on the common attribute 'b'. The resulting set contains tuples from the combined schemas
        ('a', 'b', 'c') with membership values computed using the `FuzzyAnd.MIN`,
        set as the default `AND` truth function in the class `FuzzyLogic`.
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"

    schema1 = self.get_schema()
    schema2 = set2.get_schema()
    schema2_rem = list(schema2)
    indexes_to_check_1 = []
    indexes_to_check_2 = []
    indexes_to_insert_2 = list(range(0, len(schema2)))
    for index, var in enumerate(schema1):
        if var in schema2:
            indexes_to_check_1.append(index)
            indexes_to_check_2.append(schema2.index(var))
            indexes_to_insert_2.remove(schema2.index(var))
            schema2_rem.remove(var)
    assert len(indexes_to_check_1) > 0, "'set1' and 'set2' must have at least one set in common in their schema!"

    new_mf = {}
    for element1, membership1 in self.__mf.items():
        for element2, membership2 in set2.to_dictionary().items():
            to_insert = True
            for index in range(0, len(indexes_to_check_1)):
                if element1[indexes_to_check_1[index]] != element2[indexes_to_check_2[index]]:
                    to_insert = False
                    break
            if to_insert:
                new_elem = list(element1)
                for index in indexes_to_insert_2:
                    new_elem.append(element2[index])
                new_membership = FuzzyLogic.and_fun(membership1, membership2)
                if new_membership > .0:
                    new_mf[tuple(new_elem)] = new_membership
    return DiscreteFuzzySet(schema1 + tuple(schema2_rem), new_mf)

__mul__(set2)

Computes the Cartesian product of two DiscreteFuzzySet instances.

The Cartesian product creates new elements from the two sets as their concatenation and applies FuzzyLogic.and_fun() operation to their membership values.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
Raises:
  • AssertionError

    If set2 is not an instance of DiscreteFuzzySet or if self and set2 have overlapping schemas.

Returns:
  • DiscreteFuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet representing the Cartesian product of self and set2.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.4})
>>> set_b = DiscreteFuzzySet(('b',), {('1',): 0.8, ('2',): 0.6})
>>> result = set_a * set_b
>>> print(result.to_dictionary())
{('x', '1'): 0.7, ('x', '2'): 0.6, ('y', '1'): 0.4, ('y', '2'): 0.4}

In this example, the Cartesian product of set_a and set_b is computed by pairing each element from set_a with each element from set_b. The resulting set contains tuples representing all possible combinations of elements from both sets, with membership values calculated using the FuzzyAnd.MIN, set as the default AND truth function in the class FuzzyLogic.

Source code in pyPRUF/fuzzy_sets.py
def __mul__(self, set2: FuzzySet) -> FuzzySet: # cartesian_product: *, *=
    """Computes the Cartesian product of two `DiscreteFuzzySet` instances.

    The Cartesian product creates new elements from the two sets as their concatenation
    and applies `FuzzyLogic.and_fun()` operation to their membership values.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
            combine with.

    Raises:
        AssertionError: If `set2` is not an instance of
            `DiscreteFuzzySet` or if `self` and `set2`
            have overlapping schemas.

    Returns:
        DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
            Cartesian product of `self` and `set2`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.4})
        >>> set_b = DiscreteFuzzySet(('b',), {('1',): 0.8, ('2',): 0.6})
        >>> result = set_a * set_b
        >>> print(result.to_dictionary())
        {('x', '1'): 0.7, ('x', '2'): 0.6, ('y', '1'): 0.4, ('y', '2'): 0.4}

        In this example, the Cartesian product of `set_a` and `set_b` is computed by
        pairing each element from `set_a` with each element from `set_b`. The resulting set
        contains tuples representing all possible combinations of elements from both sets,
        with membership values calculated using the `FuzzyAnd.MIN`,
        set as the default `AND` truth function in the class `FuzzyLogic`.
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
    set1_schema = self.get_schema()
    set2_schema = set2.get_schema()
    for var in set2_schema:
        assert var not in set1_schema, "'" + var + "' is in both schemas: " + str(set1_schema) + "\n'set1' and 'set2' must have different schemas!"

    new_mf = {}
    for element1, membership1 in self.__mf.items():
        for element2, membership2 in set2.to_dictionary().items():
            new_membership = FuzzyLogic.and_fun(membership1, membership2)
            if new_membership > .0:
                new_mf[element1 + element2] = new_membership
    fs = DiscreteFuzzySet(set1_schema + set2_schema, new_mf)
    return fs

__or__(set2)

Computes the fuzzy union of two DiscreteFuzzySet instances.

The fuzzy union is calculated by applying the FuzzyLogic.or_fun() function to the membership values of corresponding elements in both sets.

Parameters:
Raises:
  • AssertionError

    If set2 is not an instance of DiscreteFuzzySet or if self and set2 do not have the same schema.

Returns:
  • DiscreteFuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet representing the union of self and set2.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
>>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
>>> result = set_a | set_b
>>> print(result.to_dictionary())
{('x',): 0.7, ('y',): 0.8}

In this example, the union of set_a and set_b is computed by applying the FuzzyOr.MAX, set as the default OR truth function of the class FuzzyLogic, function to the membership values of corresponding elements. The resulting set contains elements with membership values that represent the maximum of the corresponding values in set_a and set_b.

Source code in pyPRUF/fuzzy_sets.py
def __or__(self, set2: FuzzySet) -> FuzzySet: # union: |, |=
    """Computes the fuzzy union of two `DiscreteFuzzySet` instances.

    The fuzzy union is calculated by applying the `FuzzyLogic.or_fun()`
    function to the membership values of corresponding elements in both sets.

    Args:
        set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
            unite with `self`.

    Raises:
        AssertionError: If `set2` is not an instance of
            `DiscreteFuzzySet` or if `self` and `set2`
            do not have the same schema.

    Returns:
        DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
            union of `self` and `set2`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5})
        >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.8})
        >>> result = set_a | set_b
        >>> print(result.to_dictionary())
        {('x',): 0.7, ('y',): 0.8}

        In this example, the union of `set_a` and `set_b` is computed by applying the `FuzzyOr.MAX`,
        set as the default `OR` truth function of the class `FuzzyLogic`,
        function to the membership values of corresponding elements. The resulting set
        contains elements with membership values that represent the maximum of the
        corresponding values in `set_a` and `set_b`.
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
    assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

    new_mf = set2.to_dictionary()
    for element, membership1 in self.__mf.items():
        new_membership = FuzzyLogic.or_fun(membership1, set2[element])
        if new_membership > .0:
            new_mf[element] = new_membership
    fs = DiscreteFuzzySet(self.get_schema(), new_mf)
    return fs

__repr__()

Returns the Zadeh inline string representation of this fuzzy set.

Returns:
  • str( str ) –

    Returns the Zadeh inline string representation of this fuzzy set.

Source code in pyPRUF/fuzzy_sets.py
def __repr__(self) -> str: # differentia
    """Returns the Zadeh inline string representation of this
    fuzzy set.

    Returns:
        str: Returns the Zadeh inline string
            representation of this fuzzy set.
    """
    s = ''
    for value, membership in self.__mf.items():
        if membership > .0:
            s += str(membership) + '/' + str(value) + ' + '
    if len(self.__mf.items()) > 0:
        return s[:-3]
    return '∅'

__setitem__(element, membership)

Adds or updates (the membership value of) an element in the DiscreteFuzzySet.

The element must be a tuple with the same length as the schema, and the membership must be a float value in the interval (0, 1].

Parameters:
  • element (tuple) –

    The element to add or update in the DiscreteFuzzySet.

  • membership (float) –

    The membership value associated with the element.

Raises:
  • AssertionError

    If membership is not a float in the interval (0, 1] or if element is not a tuple with the same length as the schema of the current fuzzy set.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7})
>>> set_a[('y',)] = 0.5
>>> set_a[('x',)] = 0.8
>>> print(set_a.to_dictionary())
{('x',): 0.8, ('y',): 0.5}

In this example, the element ('y',) is added to set_a with a membership value of 0.5, and the membership value of the existing element ('x',) is updated to 0.8. The resulting set reflects these changes.

Source code in pyPRUF/fuzzy_sets.py
def __setitem__(self, element: tuple, membership: float) -> None: # add/update_element: []
    """Adds or updates (the membership value of) an element in the `DiscreteFuzzySet`.

    The `element` must be a tuple with the same length as the schema,
    and the `membership` must be a float value in the interval (0, 1].

    Args:
        element (tuple): The element to add or update in the
            `DiscreteFuzzySet`.
        membership (float): The membership value associated with the
            `element`.

    Raises:
        AssertionError: If `membership` is not a float in the
            interval (0, 1] or if `element` is not a tuple with
            the same length as the schema of the current fuzzy set.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7})
        >>> set_a[('y',)] = 0.5
        >>> set_a[('x',)] = 0.8
        >>> print(set_a.to_dictionary())
        {('x',): 0.8, ('y',): 0.5}

        In this example, the `element` `('y',)` is added to `set_a` with a membership value of 0.5,
        and the membership value of the existing element `('x',)` is updated to 0.8. The resulting
        set reflects these changes.
    """
    assert isinstance(membership, float), "'membership' must be a float!"
    assert .0 < membership and membership <= 1.0, "'membership' must be in (0, 1] interval!"
    assert isinstance(element, tuple), "'element' must be a tuple! "
    assert len(self.__schema) == len(element), "'element' must be a tuple of the same length of the schema!"

    self.__mf[element] = membership

__str__()

Returns string representation of this fuzzy set.

Returns:
  • str( str ) –

    Returns the string representation of this fuzzy set.

Source code in pyPRUF/fuzzy_sets.py
def __str__(self) -> str: # differentia
    """Returns string representation of this
    fuzzy set.

    Returns:
        str: Returns the string representation of this
            fuzzy set.
    """
    return self.__repr__()

__sub__(set2)

Computes the difference (relative complement) between two DiscreteFuzzySet instances.

The difference is calculated by applying FuzzyLogic.and_fun() operation between the membership values of self and the negated membership values of set2 calculated using FuzzyLogic.not_fun().

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
  • set2 (DiscreteFuzzySet) –

    The DiscreteFuzzySet instance to subtract from self.

Raises:
  • AssertionError

    If set2 is not an instance of DiscreteFuzzySet or if self and set2 do not have the same schema.

Returns:
  • DiscreteFuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet representing the difference between self and set2.

Examples:

>>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5, ('z', ): 0.3})
>>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.5}, ('z', ): 1.0)
>>> result = set_a - set_b
>>> print(result.to_dictionary())
{('x',): 0.6, ('y',): 0.5}

In this example, the result is computed using the FuzzyAnd.MIN and FuzzyNot.STANDARD truth functions, set as the default AND and NOT truth functions of the class FuzzyLogic. The resulting set contains only the elements with a positive membership value.

Source code in pyPRUF/fuzzy_sets.py
def __sub__(self, set2: FuzzySet) -> FuzzySet: # differenza: - # corrisponde al complemento relativo insiemistico di set2 rispetto a set1
    """Computes the difference (relative complement) between two `DiscreteFuzzySet` instances.

    The difference is calculated by applying `FuzzyLogic.and_fun()` operation between the
    membership values of `self` and the negated membership values of `set2` calculated
    using `FuzzyLogic.not_fun()`.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
            subtract from `self`.

    Raises:
        AssertionError: If `set2` is not an instance of
            `DiscreteFuzzySet` or if `self` and `set2`
            do not have the same schema.

    Returns:
        DiscreteFuzzySet: A new `DiscreteFuzzySet` representing the
            difference between `self` and `set2`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('a',), {('x',): 0.7, ('y',): 0.5, ('z', ): 0.3})
        >>> set_b = DiscreteFuzzySet(('a',), {('x',): 0.4, ('y',): 0.5}, ('z', ): 1.0)
        >>> result = set_a - set_b
        >>> print(result.to_dictionary())
        {('x',): 0.6, ('y',): 0.5}

        In this example, the result is computed using the `FuzzyAnd.MIN` and
        `FuzzyNot.STANDARD` truth functions, set as the default `AND` and `NOT` truth
        functions of the class `FuzzyLogic`.
        The resulting set contains only the elements with a positive membership value.
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
    assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

    new_mf = {}
    for element, membership1 in self.__mf.items():
        new_membership = FuzzyLogic.and_fun(membership1, FuzzyLogic.not_fun(set2[element]))
        if new_membership > .0:
            new_mf[element] = new_membership
    fs = DiscreteFuzzySet(self.get_schema(), new_mf)
    return fs

__truediv__(set2)

Computes the proportion of elements in the intersection of self and set2 relative to the total number of elements in set2.

Parameters:
Raises:
  • AssertionError

    If set2 is not an instance of DiscreteFuzzySet, if the cardinality of set2 is zero or if the assumprions of the fuzzy union are breached.

Returns:
  • float( float ) –

    The proportion of elements in the intersection of self and set2 relative to set2.

Examples:

>>> set_a = DiscreteFuzzySet(('X', ), {('a', ): 0.7, ('b', ): 0.3, ('c', ): 0.5})
>>> set_b = DiscreteFuzzySet(('X', ), {('b', ): 0.4, ('c', ): 0.5, ('d', ): 0.6})
>>> proportion = set_a / set_b
>>> print(proportion)
0.5333333333333333

In this example, the intersection of set_a and set_b contains the elements {('b', ), ('c', )} with a combined cardinality of 2, while set_b has a cardinality of 3. Therefore, the proportion returned is 2/3.

Source code in pyPRUF/fuzzy_sets.py
def __truediv__(self, set2) -> float: # proportion: /
    """Computes the proportion of elements in the intersection of `self` and `set2`
    relative to the total number of elements in `set2`.

    Args:
        set2 (DiscreteFuzzySet): The `DiscreteFuzzySet` instance to
            divide by.

    Raises:
        AssertionError: If `set2` is not an instance of
            `DiscreteFuzzySet`, if the cardinality of `set2` is zero
            or if the assumprions of the fuzzy union are breached.

    Returns:
        float: The proportion of elements in the intersection of
            `self` and `set2` relative to `set2`.

    Examples:
        >>> set_a = DiscreteFuzzySet(('X', ), {('a', ): 0.7, ('b', ): 0.3, ('c', ): 0.5})
        >>> set_b = DiscreteFuzzySet(('X', ), {('b', ): 0.4, ('c', ): 0.5, ('d', ): 0.6})
        >>> proportion = set_a / set_b
        >>> print(proportion)
        0.5333333333333333

        In this example, the intersection of `set_a` and `set_b` contains the elements
        {('b', ), ('c', )} with a combined cardinality of 2, while `set_b` has a cardinality of 3.
        Therefore, the proportion returned is 2/3.
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
    assert set2.cardinality() > 0, "'set2' must have cardinality greater than 0!" 
    new_set = self & set2
    return new_set.cardinality() / set2.cardinality()

apply(operator)

Applies a fuzzy unary operator to all membership values in the fuzzy set.

This method applies a specified 'FuzzyUnaryOperator' to each membership value in the fuzzy set, producing a new fuzzy set with the modified membership values.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
  • operator (FuzzyUnaryOperator) –

    A unary operator that operates on the membership values of the fuzzy set.

Raises:
  • AssertionError

    If operator is not of type FuzzyUnaryOperator.

Returns:
  • FuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet object where the membership values have been modified according to the given unary operator.

Examples:

>>> operator = LinguisticModifiers.VERY
>>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8})
>>> modified_set = original_set.apply(operator)
>>> print(modified_set.to_dictionary())
{('a', 'b'): 0.25, ('c', 'd'): 0.64}
Source code in pyPRUF/fuzzy_sets.py
def apply(self, operator: FuzzyUnaryOperator) -> FuzzySet:
    """Applies a fuzzy unary operator to all membership values in the fuzzy set.

    This method applies a specified 'FuzzyUnaryOperator' to each membership value in the fuzzy set,
    producing a new fuzzy set with the modified membership values.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        operator (FuzzyUnaryOperator): A unary operator that
            operates on the membership values of the fuzzy set.

    Raises:
        AssertionError: If `operator` is not of type
            `FuzzyUnaryOperator`.

    Returns:
        FuzzySet: A new `DiscreteFuzzySet` object where the
            membership values have been modified according to the given
            unary operator.

    Examples:
        >>> operator = LinguisticModifiers.VERY
        >>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8})
        >>> modified_set = original_set.apply(operator)
        >>> print(modified_set.to_dictionary())
        {('a', 'b'): 0.25, ('c', 'd'): 0.64}
    """
    assert isinstance(operator, FuzzyUnaryOperator), "'operator' must be of type 'FuzzyUnaryOperator'!"
    new_mf = {}
    for element, membership in self.__mf.items():
        new_membership = operator(membership)
        if new_membership > .0:
            new_mf[element] = new_membership
    return DiscreteFuzzySet(self.get_schema(), new_mf)

cardinality()

Computes the cardinality of the fuzzy set.

This method calculates the total sum of all membership values in the fuzzy set. The cardinality represents the aggregate degree of membership of all elements in the set.

Returns:
  • float( float ) –

    The sum of the membership values of all elements in the fuzzy set.

Examples:

>>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.4, ('c', 'd'): 0.6})
>>> total_cardinality = fuzzy_set.cardinality()
>>> print(total_cardinality)
1.0
Source code in pyPRUF/fuzzy_sets.py
def cardinality(self) -> float:
    """Computes the cardinality of the fuzzy set.

    This method calculates the total sum of all membership values in the fuzzy set. The cardinality
    represents the aggregate degree of membership of all elements in the set.

    Returns:
        float: The sum of the membership values of all elements in
            the fuzzy set.

    Examples:
        >>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.4, ('c', 'd'): 0.6})
        >>> total_cardinality = fuzzy_set.cardinality()
        >>> print(total_cardinality)
        1.0
    """
    memberships_sum = 0.0
    for value in self.__mf.values():
        memberships_sum += value
    return memberships_sum

collapse(operator)

Collapses the fuzzy set into a single membership value using a specified binary operator.

This method reduces the fuzzy set to a single membership value by applying a binary operator iteratively across all membership values in the fuzzy set. The operator must be an instance of FuzzyBinaryOperator, and the fuzzy set must contain at least two elements.

Parameters:
Raises:
  • AssertionError

    If 'operator' is not of type FuzzyBinaryOperator or if the fuzzy set contains fewer than two elements.

Returns:
  • float( float ) –

    The resulting membership value after applying the binary operator across all elements.

Examples:

>>> operator = FuzzyAnd.MIN  # Example operator: minimum of two values
>>> fuzzy_set = DiscreteFuzzySet(schema, {('a', 'b'): 0.4, ('c', 'd'): 0.6})
>>> collapsed_value = fuzzy_set.collapse(operator)
>>> print(collapsed_value)
0.4
Source code in pyPRUF/fuzzy_sets.py
def collapse(self, operator: FuzzyBinaryOperator) -> float: # differentia # NON TESTATO : Banale
    """Collapses the fuzzy set into a single membership value using a specified binary operator.

    This method reduces the fuzzy set to a single membership value by applying a binary operator
    iteratively across all membership values in the fuzzy set. The operator must be an instance
    of `FuzzyBinaryOperator`, and the fuzzy set must contain at least two elements.

    Args:
        operator (FuzzyBinaryOperator): A binary operator used to
            collapse the fuzzy set.

    Raises:
        AssertionError: If 'operator' is not of type
            `FuzzyBinaryOperator` or if the fuzzy set contains fewer
            than two elements.

    Returns:
        float: The resulting membership value after applying the
            binary operator across all elements.

    Examples:
        >>> operator = FuzzyAnd.MIN  # Example operator: minimum of two values
        >>> fuzzy_set = DiscreteFuzzySet(schema, {('a', 'b'): 0.4, ('c', 'd'): 0.6})
        >>> collapsed_value = fuzzy_set.collapse(operator)
        >>> print(collapsed_value)
        0.4
    """
    assert isinstance(operator, FuzzyBinaryOperator), "'operator' must be of type 'FuzzyBinaryOperator'!"
    assert len(self.__mf.keys()) > 1, "The fuzzy set must have at least two elements!"
    memberships = list(self.__mf.values())
    if len(memberships) > 0:
        result = memberships[0]
        for membership in memberships[1:]:
            result = operator(result, membership)
    else:
        result = .0
    return result

compatibility(set2)

Computes the compatibility of the current fuzzy set with another fuzzy set.

This method calculates a new fuzzy set based on the membership values of set2, with each membership value in the new set representing the maximum compatibility with the corresponding membership value in the current set. Both fuzzy sets must have the same schema for the operation to be valid.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
  • set2 (FuzzySet) –

    Another fuzzy set to compare with. It must have the same schema as the current fuzzy set.

Raises:
  • AssertionError

    If set2 is not of type DiscreteFuzzySet or if the schemas of the two fuzzy sets do not match.

Returns:
  • FuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet object where each element is associated with the maximum membership value between the two fuzzy sets.

Examples:

>>> fuzzy_set1 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.3, ('d', 'e'): 0.6, ('c', 'd'): 0.8, ('h', 'i'): 0.4})
>>> fuzzy_set2 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('d', 'e'): 0.5, ('e', 'f'): 0.7, ('c', 'd'): 0.2})
>>> compatible_set = fuzzy_set1.compatibility(fuzzy_set2)
>>> print(compatible_set.to_dictionary())
{(0.5, ): 0.6, (0.2, ): 0.8}
Source code in pyPRUF/fuzzy_sets.py
def compatibility(self, set2: FuzzySet) -> FuzzySet:
    """Computes the compatibility of the current fuzzy set with another fuzzy set.

    This method calculates a new fuzzy set based on the membership values of `set2`,
    with each membership value in the new set representing the maximum compatibility
    with the corresponding membership value in the current set.
    Both fuzzy sets must have the same schema for the operation to be valid.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        set2 (FuzzySet): Another fuzzy set to compare with.
            It must have the same schema as the current fuzzy set.

    Raises:
        AssertionError: If `set2` is not of type `DiscreteFuzzySet`
            or if the schemas of the two fuzzy sets do not match.

    Returns:
        FuzzySet: A new `DiscreteFuzzySet` object where each element
            is associated with the maximum membership value between the
            two fuzzy sets.

    Examples:
        >>> fuzzy_set1 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.3, ('d', 'e'): 0.6, ('c', 'd'): 0.8, ('h', 'i'): 0.4})
        >>> fuzzy_set2 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('d', 'e'): 0.5, ('e', 'f'): 0.7, ('c', 'd'): 0.2})
        >>> compatible_set = fuzzy_set1.compatibility(fuzzy_set2)
        >>> print(compatible_set.to_dictionary())
        {(0.5, ): 0.6, (0.2, ): 0.8}
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'!"
    assert self.get_schema() == set2.get_schema(), "'set2' must have the same schema!"

    new_mf = {}
    for element, membership in set2.to_dictionary().items():
        key = (membership, )
        if key in new_mf.keys():
            new_mf[key] = max(self[element], new_mf[key])
        elif self[element] > .0:
            new_mf[key] = self[element]

    return DiscreteFuzzySet(('membership', ), new_mf)

consistency(reference_set)

Computes the consistency level between the fuzzy set and a reference fuzzy set.

This method calculates the consistency of the current fuzzy set with a provided reference set. Consistency is defined as the maximum of the fuzzy AND operation applied to the membership values of corresponding elements in both sets. The two fuzzy sets must have the same schema for this operation to be valid.

Parameters:
  • reference_set (FuzzySet) –

    A reference fuzzy set to compare with. It must have the same schema as the current fuzzy set.

Raises:
  • AssertionError

    If reference_set is not of type DiscreteFuzzySet or if the schemas of the two fuzzy sets do not match.

Returns:
  • float( float ) –

    The consistency value, representing the maximum consistency level between the two sets.

Examples:

>>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.7, ('c', 'd'): 0.4})
>>> reference_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.6, ('c', 'd'): 0.5})
>>> consistency_value = fuzzy_set.consistency(reference_set)
>>> print(consistency_value)
0.6
Source code in pyPRUF/fuzzy_sets.py
def consistency(self, reference_set: FuzzySet) -> float:
    """Computes the consistency level between the fuzzy set and a reference fuzzy set.

    This method calculates the consistency of the current fuzzy set with a provided reference set.
    Consistency is defined as the maximum of the fuzzy AND operation applied to the membership
    values of corresponding elements in both sets. The two fuzzy sets must have the same schema
    for this operation to be valid.

    Args:
        reference_set (FuzzySet): A reference fuzzy set to compare
            with. It must have the same schema as the current fuzzy
            set.

    Raises:
        AssertionError: If `reference_set` is not of type
            `DiscreteFuzzySet` or if the schemas of the two fuzzy
            sets do not match.

    Returns:
        float: The consistency value, representing the maximum
            consistency level between the two sets.

    Examples:
        >>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.7, ('c', 'd'): 0.4})
        >>> reference_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.6, ('c', 'd'): 0.5})
        >>> consistency_value = fuzzy_set.consistency(reference_set)
        >>> print(consistency_value)
        0.6
    """
    assert isinstance(reference_set, DiscreteFuzzySet), "'reference_set' must be of type 'DiscreteFuzzySet'!"
    assert self.get_schema() == reference_set.get_schema(), "'reference_set' must have the same schema!"

    consistency = .0
    for element, mu1 in reference_set.to_dictionary().items():
        consistency = max(FuzzyLogic.and_fun(mu1, self[element]), consistency)

    return consistency

cylindrical_extension(set2)

Computes the cylindrical extension of two fuzzy sets over a combined schema.

This method extends the current fuzzy set and set2 to a common schema that includes all attributes from both schemas. The resulting sets have the same schema, with membership values adjusted according to the cylindrical extension. The new schema is created by merging the attributes of the two schemas:

  • First the common attributes are added in the same order as their appear in the self schema
  • Second the unique attributes of the self schema are concatenated in the same order as their appear in it
  • Third the unique attributes of the set2 schema are concatenated in the same order as their appear in it
Parameters:
  • set2 (DiscreteFuzzySet) –

    Another fuzzy set to be extended. It must be an instance of DiscreteFuzzySet.

Raises:
  • AssertionError

    If set2 is not of type DiscreteFuzzySet.

Returns:
  • Tuple[FuzzySet, FuzzySet]

    A tuple containing two DiscreteFuzzySet objects

    • The first is the cylindrical extension of the original set.
    • The second is the cylindrical extension of set2.

Examples:

>>> set1 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.4})
>>> set2 = DiscreteFuzzySet(('y', 'z'), {('e', 'f'): 0.7, ('g', 'h'): 0.3})
>>> extended_set1, extended_set2 = set1.cylindrical_extension(set2)
>>> print(extended_set1.get_schema())
('y', 'x', 'z')
>>> print(extended_set2.get_schema())
('y', 'x', 'z')
>>> print(extended_set1.to_dictionary())
{('b', 'a', 'f'): 0.5, ('b', 'a', 'h'): 0.5, ('d', 'c', 'f'): 0.4, ('d', 'c', 'h'): 0.4}
>>> print(extended_set2.to_dictionary())
{('e', 'a', 'f'): 0.7, ('e', 'c', 'f'): 0.7, ('g', 'a', 'h'): 0.3, ('g', 'c', 'h'): 0.3}
Source code in pyPRUF/fuzzy_sets.py
def cylindrical_extension(self, set2: FuzzySet) -> Tuple[FuzzySet, FuzzySet]:
    """Computes the cylindrical extension of two fuzzy sets over a combined schema.

    This method extends the current fuzzy set and `set2` to a common schema
    that includes all attributes from both schemas. The resulting sets have the same schema, with
    membership values adjusted according to the cylindrical extension. The new schema is created by
    merging the attributes of the two schemas:\n
    - First the common attributes are added in the same order as their appear in the `self` schema
    - Second the unique attributes of the `self` schema are concatenated in the same order as their appear in it
    - Third the unique attributes of the `set2` schema are concatenated in the same order as their appear in it

    Args:
        set2 (DiscreteFuzzySet): Another fuzzy set to be extended.
            It must be an instance of `DiscreteFuzzySet`.

    Raises:
        AssertionError: If `set2` is not of type `DiscreteFuzzySet`.

    Returns:
        (Tuple[FuzzySet, FuzzySet]): A tuple containing two
            `DiscreteFuzzySet` objects \n
            - The first is the cylindrical
            extension of the original set.
            - The second is the
            cylindrical extension of `set2`.

    Examples:
        >>> set1 = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.4})
        >>> set2 = DiscreteFuzzySet(('y', 'z'), {('e', 'f'): 0.7, ('g', 'h'): 0.3})
        >>> extended_set1, extended_set2 = set1.cylindrical_extension(set2)
        >>> print(extended_set1.get_schema())
        ('y', 'x', 'z')
        >>> print(extended_set2.get_schema())
        ('y', 'x', 'z')
        >>> print(extended_set1.to_dictionary())
        {('b', 'a', 'f'): 0.5, ('b', 'a', 'h'): 0.5, ('d', 'c', 'f'): 0.4, ('d', 'c', 'h'): 0.4}
        >>> print(extended_set2.to_dictionary())
        {('e', 'a', 'f'): 0.7, ('e', 'c', 'f'): 0.7, ('g', 'a', 'h'): 0.3, ('g', 'c', 'h'): 0.3}
    """
    assert isinstance(set2, DiscreteFuzzySet), "'set2' must be of type 'DiscreteFuzzySet'"
    schema1 = self.get_schema()
    schema2 = set2.get_schema()
    to_insert_1 = list(range(len(schema1)))
    to_insert_2 = list(range(len(schema2)))
    common1 = []
    common2 = []
    for index, var in enumerate(schema1):
        if var in schema2:
            common1.append(index)
            to_insert_1.remove(index)
            index2 = schema2.index(var)
            to_insert_2.remove(index2)
            common2.append(index2)

    new_schema = []
    for index in common1:
        new_schema.append(schema1[index])
    for index in to_insert_1:
        new_schema.append(schema1[index])
    for index in to_insert_2:
        new_schema.append(schema2[index])
    new_schema = tuple(new_schema)

    mf1_ext = {}
    mf2_ext = {}
    for element1, membership1 in self.__mf.items():
        for element2, membership2 in set2.to_dictionary().items():

            new_elem = []
            for index in common1:
                new_elem.append(element1[index])
            for index in to_insert_1:
                new_elem.append(element1[index])
            for index in to_insert_2:
                new_elem.append(element2[index])
            mf1_ext[tuple(new_elem)] = membership1

            new_elem = []
            for index in common2:
                new_elem.append(element2[index])
            for index in to_insert_1:
                new_elem.append(element1[index])
            for index in to_insert_2:
                new_elem.append(element2[index])
            mf2_ext[tuple(new_elem)] = membership2

    return DiscreteFuzzySet(new_schema, mf1_ext), DiscreteFuzzySet(new_schema, mf2_ext)

elements()

Returns the set of all tuples in this fuzzy relation.

Returns:
  • set( set ) –

    The set of all tuples in this fuzzy relation.

Source code in pyPRUF/fuzzy_sets.py
def elements(self) -> set: # differentia
    """Returns the set of all tuples in this fuzzy relation.

    Returns:
        set: The set of all tuples in this fuzzy relation.
    """
    return set(self.__mf.keys())

extension_principle(function, out_schema)

Computes the fuzzy extension of the function defined on the tuples of this fuzzy set.

This method applies a specified function to each element in the fuzzy set, mapping it to a new schema (out_schema). The resulting fuzzy set consists of the function's output as the new elements and their associated membership values. If multiple elements map to the same output, their membership values are combined using the fuzzy OR operation.

Parameters:
  • function (Callable) –

    A callable function that maps

  • out_schema (tuple) –

    The schema of the resulting fuzzy elements from the original schema to the output schema. set after applying the function.

Raises:
  • AssertionError

    If 'function' is not a callable.

Returns:
  • FuzzySet( FuzzySet ) –

    A new 'DiscreteFuzzySet' object representing the fuzzy extension of the original given function.

Examples:

>>> def example_function(element):
...     if element == ('a', 'b'):
...         return ('c', )
...     elif element == ('c', 'd'):
...         return ('c', )
...     elif element == ('e', 'f'):
...         return ('d', )
>>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8, ('e', 'f'): 0.4})
>>> out_schema = ('z',)
>>> fuzzy_fun = original_set.extension_principle(example_function, out_schema)
>>> print(fuzzy_fun.to_dictionary())
{('c', ): 0.8, ('d', ): 0.4}
>>> print(fuzzy_fun.get_schema())
('z',)
Source code in pyPRUF/fuzzy_sets.py
def extension_principle(self, function: Callable, out_schema: tuple) -> FuzzySet: # NON TESTATO
    """Computes the fuzzy extension of the function defined on the tuples of this fuzzy set.

    This method applies a specified function to each element in the fuzzy set, mapping it to a new
    schema (`out_schema`). The resulting fuzzy set consists of the function's output as the new
    elements and their associated membership values. If multiple elements map to the same output,
    their membership values are combined using the fuzzy OR operation.

    Args:
        function (Callable): A callable function that maps
        out_schema (tuple): The schema of the resulting fuzzy
            elements from the original schema to the output schema.
            set after applying the function.

    Raises:
        AssertionError: If 'function' is not a callable.

    Returns:
        FuzzySet: A new 'DiscreteFuzzySet' object representing
            the fuzzy extension of the original given function.

    Examples:
        >>> def example_function(element):
        ...     if element == ('a', 'b'):
        ...         return ('c', )
        ...     elif element == ('c', 'd'):
        ...         return ('c', )
        ...     elif element == ('e', 'f'):
        ...         return ('d', )

        >>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8, ('e', 'f'): 0.4})
        >>> out_schema = ('z',)
        >>> fuzzy_fun = original_set.extension_principle(example_function, out_schema)
        >>> print(fuzzy_fun.to_dictionary())
        {('c', ): 0.8, ('d', ): 0.4}
        >>> print(fuzzy_fun.get_schema())
        ('z',)
    """
    assert isinstance(function, Callable), "'function' must be a callable function!"
    new_mf = {}
    for element, membership in self.__mf.items():
        y = function(element)
        if y in new_mf:
            new_mf[y] = FuzzyLogic.or_fun(new_mf[y], membership)
        else:
            new_mf[y] = membership
    return DiscreteFuzzySet(out_schema, new_mf)

get_schema()

Retrieves the schema of the fuzzy set.

This method returns the schema of the fuzzy set as a tuple of strings. The schema consists of the attribute names, which are associated with the sets that define the fuzzy set.

Returns:
  • Tuple[str]

    A tuple representing the schema of the fuzzy set.

Examples:

>>> fuzzy_set = DiscreteFuzzySet(('x', 'y', 'z'), {('a', 'b', 'c'): 0.5})
>>> schema = fuzzy_set.get_schema()
>>> print(schema)
('x', 'y', 'z').
Source code in pyPRUF/fuzzy_sets.py
def get_schema(self) -> Tuple[str]: # NON TESTATO : Banale
    """Retrieves the schema of the fuzzy set.

    This method returns the schema of the fuzzy set as a tuple of strings. The schema consists
    of the attribute names, which are associated with the sets that define the fuzzy set.

    Returns:
        (Tuple[str]): A tuple representing the schema of the fuzzy set.

    Examples:
        >>> fuzzy_set = DiscreteFuzzySet(('x', 'y', 'z'), {('a', 'b', 'c'): 0.5})
        >>> schema = fuzzy_set.get_schema()
        >>> print(schema)
        ('x', 'y', 'z').
    """
    return tuple(self.__schema)

items()

Returns the set of all (tuple, memberships) pairs in this fuzzy relation.

Returns:
  • set( set ) –

    The set of all (tuple, memberships) pairs in this fuzzy relation.

Source code in pyPRUF/fuzzy_sets.py
def items(self) -> set: # differentia
    """Returns the set of all (tuple, memberships) pairs
    in this fuzzy relation.

    Returns:
        set: The set of all (tuple, memberships) pairs
            in this fuzzy relation.
    """
    return set(self.__mf.items())

mean_cardinality()

Calculates the mean cardinality of the fuzzy set.

This method computes the average membership value of all elements in the fuzzy set. The mean cardinality provides an indication of the average degree of membership of the elements in the set.

Returns:
  • float( float ) –

    The mean membership value of the fuzzy set. If the set is empty, the method returns 0.0.

Examples:

>>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.4, ('c', 'd'): 0.6})
>>> mean_cardinality = fuzzy_set.mean_cardinality()
>>> print(mean_cardinality)
0.5
Source code in pyPRUF/fuzzy_sets.py
def mean_cardinality(self) -> float:
    """Calculates the mean cardinality of the fuzzy set.

    This method computes the average membership value of all elements in the fuzzy set. The mean
    cardinality provides an indication of the average degree of membership of the elements in the set.

    Returns:
        float: The mean membership value of the fuzzy set. If the
            set is empty, the method returns 0.0.

    Examples:
        >>> fuzzy_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.4, ('c', 'd'): 0.6})
        >>> mean_cardinality = fuzzy_set.mean_cardinality()
        >>> print(mean_cardinality)
        0.5
    """
    memberships_sum = 0.0
    n = 0
    for value in self.__mf.values():
        memberships_sum += value
        n += 1
    if n:
        return memberships_sum / n
    else:
        return .0

memberships()

Returns the set of all memberships in this fuzzy relation.

Returns:
  • set( set ) –

    The set of all memberships in this fuzzy relation.

Source code in pyPRUF/fuzzy_sets.py
def memberships(self) -> set: # differentia
    """Returns the set of all memberships in this fuzzy relation.

    Returns:
        set: The set of all memberships in this fuzzy relation.
    """
    return set(self.__mf.values())

particularization(assignment)

Performs particularization of the fuzzy set based on a given assignment.

This method creates a new fuzzy set by specializing the current set according to the provided assignment. The assignment is a dictionary where each key is an attribute of the schema, and each value specifies a particular value or a fuzzy set. The resulting fuzzy set includes only the elements that match the specified assignments. If a variable in the assignment maps to a specific value, only elements with that value are retained. If it maps to another fuzzy set, the membership values are combined using fuzzy AND operations.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
  • assignment (dict) –

    A dictionary where each key is an attribute from the schema of the fuzzy set, and each value is either a specific value or a DiscreteFuzzySet to match against.

Raises:
  • AssertionError

    If assignment is not a dictionary, if any key is not in the fuzzy set's schema, or if the assignment values do not match the expected types.

Returns:
  • FuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet object that represents the particularized fuzzy set.

Examples:

>>> schema = ('x', 'y')
>>> fuzzy_set = DiscreteFuzzySet(schema, {('a', 'b'): 0.5, ('c', 'd'): 0.7})
>>> assignment = {'x': 'a', 'y': DiscreteFuzzySet(('y',), {('b',): 0.3})}
>>> particularized_set = fuzzy_set.particularization(assignment)
>>> print(particularized_set.to_dictionary())
{('a', 'b'): 0.3}
Source code in pyPRUF/fuzzy_sets.py
def particularization(self, assignment: dict) -> FuzzySet:
    """Performs particularization of the fuzzy set based on a given assignment.

    This method creates a new fuzzy set by specializing the current set according to the provided
    assignment. The assignment is a dictionary where each key is an attribute of the schema, and each
    value specifies a particular value or a fuzzy set. The resulting fuzzy set includes only the elements
    that match the specified assignments. If a variable in the assignment maps to a specific value,
    only elements with that value are retained. If it maps to another fuzzy set, the membership values
    are combined using fuzzy AND operations.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        assignment (dict): A dictionary where each key is an attribute
            from the schema of the fuzzy set, and each value is
            either a specific value or a `DiscreteFuzzySet` to match
            against.

    Raises:
        AssertionError: If `assignment` is not a dictionary, if any
            key is not in the fuzzy set's schema, or if the
            assignment values do not match the expected types.

    Returns:
        FuzzySet: A new `DiscreteFuzzySet` object that represents
            the particularized fuzzy set.

    Examples:
        >>> schema = ('x', 'y')
        >>> fuzzy_set = DiscreteFuzzySet(schema, {('a', 'b'): 0.5, ('c', 'd'): 0.7})
        >>> assignment = {'x': 'a', 'y': DiscreteFuzzySet(('y',), {('b',): 0.3})}
        >>> particularized_set = fuzzy_set.particularization(assignment)
        >>> print(particularized_set.to_dictionary())
        {('a', 'b'): 0.3}
    """
    assert isinstance(assignment, dict), "'assignment' must be a dictionary!"

    schema = self.get_schema()
    fs_assignments = []
    val_indexes = []
    fs_indexes = []
    for key in assignment:
        # assert isinstance(attr_tuple, tuple), "All keys in 'assignment' must be tuples of attributes in the schema!"
        if isinstance(key, tuple):
            indexes = []
            for attr in key:
                assert attr in schema, "'" + str(attr) + "' not in the schema of the fuzzy set!"
                indexes.append(schema.index(attr))
            assert isinstance(assignment[key], FuzzySet), "If a key in 'assignment' is a tuple, its value must be a FuzzySet!"
            fs_indexes.append(indexes)
            fs_assignments.append(key)
        else:
            assert key in schema, "'" + str(key) + "' not in the schema of the fuzzy set!"
            assert not isinstance(assignment[key], FuzzySet), "If a key in 'assignment' is an attribute name, the value must not be a FuzzySet!"
            val_indexes.append(schema.index(key))

    new_mf = {}
    for element, membership in self.__mf.items():
        for index in val_indexes:
            if element[index] != assignment[schema[index]]:
                membership = .0
                break
        for i in range(len(fs_assignments)):
            subtuple = ()
            for index in fs_indexes[i]:
                subtuple += (element[index], )
            membership = FuzzyLogic.and_fun(assignment[fs_assignments[i]][subtuple], membership)
        if membership > .0:
            new_mf[element] = membership

    fs = DiscreteFuzzySet(schema, new_mf)
    return fs

projection(subschema, operator)

Projects the fuzzy set onto a specified subschema using a binary operator.

This method creates a new fuzzy set by projecting the current set onto a given subschema. The projection involves combining membership values according to the provided binary operator. Only the elements in the new subschema are retained, and membership values are computed using the operator applied to the corresponding elements from the original fuzzy set.

WARNING: only tuples with a membership value greater than 0 are kept.

Parameters:
  • subschema (Tuple[str]) –

    A tuple representing the subschema to project onto. It must be a non-empty subset of the schema of the current fuzzy set.

  • operator (FuzzyBinaryOperator) –

    A binary operator used to combine membership values during the projection.

Raises:
  • AssertionError

    If subschema is not a non-empty tuple or if any variable in subschema is not in the schema of the fuzzy set, or if operator is not of type FuzzyBinaryOperator.

Returns:
  • FuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet object representing the projection of the original fuzzy set onto the specified subschema.

Examples:

>>> original_set = DiscreteFuzzySet(('x', 'y', 'z'), {('a', 'b', 'c'): 0.7, ('a', 'b', 'f'): 0.5, ('a', 'f', 'c'): 0.3})
>>> subschema = ('x', 'y')
>>> projected_set = original_set.projection(subschema, FuzzyAnd.MIN)
>>> print(projected_set.to_dictionary())
{('a', 'b'): 0.5, ('a', 'f'): .0.3}
Source code in pyPRUF/fuzzy_sets.py
def projection(self, subschema: Tuple[str], operator: FuzzyBinaryOperator) -> FuzzySet:
    """Projects the fuzzy set onto a specified subschema using a binary operator.

    This method creates a new fuzzy set by projecting the current set onto a given subschema. The
    projection involves combining membership values according to the provided binary operator. Only the
    elements in the new subschema are retained, and membership values are computed using the operator
    applied to the corresponding elements from the original fuzzy set.

    >> **WARNING**: only tuples with a membership value greater than 0 are kept.

    Args:
        subschema (Tuple[str]): A tuple representing the subschema
            to project onto. It must be a non-empty subset of the
            schema of the current fuzzy set.
        operator (FuzzyBinaryOperator): A binary operator used to
            combine membership values during the projection.

    Raises:
        AssertionError: If `subschema` is not a non-empty tuple or
            if any variable in `subschema` is not in the schema of
            the fuzzy set, or if `operator` is not of type
            `FuzzyBinaryOperator`.

    Returns:
        FuzzySet: A new `DiscreteFuzzySet` object representing the
            projection of the original fuzzy set onto the specified
            subschema.

    Examples:
        >>> original_set = DiscreteFuzzySet(('x', 'y', 'z'), {('a', 'b', 'c'): 0.7, ('a', 'b', 'f'): 0.5, ('a', 'f', 'c'): 0.3})
        >>> subschema = ('x', 'y')
        >>> projected_set = original_set.projection(subschema, FuzzyAnd.MIN)
        >>> print(projected_set.to_dictionary())
        {('a', 'b'): 0.5, ('a', 'f'): .0.3}
    """
    assert isinstance(subschema, tuple) and len(subschema) > 0, "'subschema' must be a non empty sub-tuple of sets from the schema of the fuzzy set!"
    assert isinstance(operator, FuzzyBinaryOperator), "'operator' must be of type 'FuzzyBinaryOperator'!"

    if subschema == ('mu', ):
        data = set(self.__mf.values())
        df = DataFrame(data=data, columns=['memberships'])
        return DiscreteFuzzySet(mf=df)

    schema = self.get_schema()
    indexes = set()
    for var in subschema:
        assert var in schema or var == 'mu', "'" + str(var) + "' not in the schema of the fuzzy set!"
        indexes.add(schema.index(var))

    new_mf = {}
    to_remove = set()
    for element, membership in self.__mf.items():
        new_tuple = []
        for index in indexes:
            new_tuple.append(element[index])

        new_tuple = tuple(new_tuple)
        if new_tuple in new_mf.keys():
            new_mf[new_tuple] = operator(new_mf[new_tuple], membership)
        else:
            new_mf[new_tuple] = membership

        if new_mf[new_tuple] == .0:
            to_remove.add(new_tuple)

    for t in to_remove:
        del new_mf[t]

    fs = DiscreteFuzzySet(subschema, new_mf)
    return fs

rename_schema(ren_dict)

Renames elements in the schema of the fuzzy set based on a provided dictionary.

This method updates the schema of the fuzzy set by renaming its attributes according to the mappings provided in ren_dict. Each key in ren_dict corresponds to an existing attribute in the schema, and the associated value is the new name for that attribute. The method performs in-place modification of the schema.

Parameters:
  • ren_dict (dict) –

    A dictionary where each key is an existing attribute in the schema and each value is the new name for that attribute.

Raises:
  • AssertionError

    If ren_dict is not a dictionary, if any key or value in ren_dict is not a string, if any key is not found in the current schema, or if any value already exists in the current schema.

Examples:

>>> original_schema = ('x', 'y', 'z')
>>> renaming_dict = {'x': 'a', 'y': 'b'}
>>> fuzzy_set = DiscreteFuzzySet(original_schema, {('x_val', 'y_val', 'z_val'): 0.7})
>>> fuzzy_set.rename_schema(renaming_dict)
>>> schema = fuzzy_set.get_schema()
>>> print(schema)
('a', 'b', 'z')
Source code in pyPRUF/fuzzy_sets.py
def rename_schema(self, ren_dict: dict) -> None: # NON TESTATO : Banale
    """Renames elements in the schema of the fuzzy set based on a provided dictionary.

    This method updates the schema of the fuzzy set by renaming its attributes according to the
    mappings provided in `ren_dict`. Each key in `ren_dict` corresponds to an existing attribute
    in the schema, and the associated value is the new name for that attribute. The method performs
    in-place modification of the schema.

    Args:
        ren_dict (dict): A dictionary where each key is an existing
            attribute in the schema and each value is the new name for
            that attribute.

    Raises:
        AssertionError: If `ren_dict` is not a dictionary, if any
            key or value in `ren_dict` is not a string, if any key
            is not found in the current schema, or if any value
            already exists in the current schema.

    Examples:
        >>> original_schema = ('x', 'y', 'z')
        >>> renaming_dict = {'x': 'a', 'y': 'b'}
        >>> fuzzy_set = DiscreteFuzzySet(original_schema, {('x_val', 'y_val', 'z_val'): 0.7})
        >>> fuzzy_set.rename_schema(renaming_dict)
        >>> schema = fuzzy_set.get_schema()
        >>> print(schema)
        ('a', 'b', 'z')
    """
    assert isinstance(ren_dict, dict), "'ren_dict' must be a dictionary!"
    for key, value in ren_dict.items():
        assert isinstance(key, str) and isinstance(key, str), "All keys and values in 'ren_dict' must be strings!"
        assert key != 'mu', "'mu' cannot be a valid attribute name in the schema!"
        assert key in self.__schema, "'" + key + "' not in this FuzzySet schema!"
        assert value not in self.__schema, "'" + value + "' already in this FuzzySet schema!"
        self.__schema[self.__schema.index(key)] = value

reorder(new_schema)

Reorders the elements of the fuzzy set according to a new schema permutation.

This method takes a new schema represented by a tuple, which should be a permutation of the current schema, and reorders the elements of the fuzzy set to match the order of the attributes in the new schema. The method returns a new FuzzySet object with the updated schema and reordered elements.

Parameters:
  • new_schema (tuple) –

    A tuple representing a permutation of the original schema. The length of new_schema must be the same as the original schema.

Raises:
  • AssertionError

    If new_schema is not a tuple, if its length differs from the original schema, or if any element in new_schema is not present in the original schema.

Returns:
  • FuzzySet( FuzzySet ) –

    A new FuzzySet object with the reordered schema and elements.

Examples:

>>> original_schema = ('x', 'y', 'z')
>>> new_schema = ('z', 'x', 'y')
>>> fuzzy_set = DiscreteFuzzySet(original_schema, {('a', 'b', 'c'): 0.5, ('d', 'e', 'f'): 0.2})
>>> reordered_set = fuzzy_set.reorder(new_schema)
>>> print(reordered_set.to_dictionary())
{('c', 'a', 'b'): 0.5, ('f', 'd', 'e'): 0.2}
Source code in pyPRUF/fuzzy_sets.py
def reorder(self, new_schema: tuple) -> FuzzySet:
    """Reorders the elements of the fuzzy set according to a new schema permutation.

    This method takes a new schema represented by a tuple, which should be a permutation of the current schema,
    and reorders the elements of the fuzzy set to match the order of the attributes in the new schema.
    The method returns a new `FuzzySet` object with the updated schema and reordered elements.

    Args:
        new_schema (tuple): A tuple representing a permutation of
            the original schema. The length of `new_schema` must be
            the same as the original schema.

    Raises:
        AssertionError: If `new_schema` is not a tuple, if its
            length differs from the original schema, or if any
            element in `new_schema` is not present in the original
            schema.

    Returns:
        FuzzySet: A new `FuzzySet` object with the reordered schema
            and elements.

    Examples:
        >>> original_schema = ('x', 'y', 'z')
        >>> new_schema = ('z', 'x', 'y')
        >>> fuzzy_set = DiscreteFuzzySet(original_schema, {('a', 'b', 'c'): 0.5, ('d', 'e', 'f'): 0.2})
        >>> reordered_set = fuzzy_set.reorder(new_schema)
        >>> print(reordered_set.to_dictionary())
        {('c', 'a', 'b'): 0.5, ('f', 'd', 'e'): 0.2}
    """
    assert isinstance(new_schema, tuple), "'tuple' must be a tuple representing a permutation of the schema!"
    assert len(new_schema) == len(self.__schema), "'tuple' must be of the same length as the original schema!"
    perm = []
    for var in new_schema:
        assert var in self.__schema, "'" + str(var) + "' not in the schema!"
        index = self.__schema.index(var)
        perm.append(index)

    new_dict = {}
    for key, value in self.__mf.items():
        new_elem = []
        for i in perm:
            new_elem.append(key[i])
        new_dict[tuple(new_elem)] = value

    return DiscreteFuzzySet(new_schema, new_dict)

select(condition)

Selects elements from the fuzzy set based on a condition.

This method filters the fuzzy set by applying a specified condition to each element and its membership value. The condition is a callable that takes a tuple consisting of the element and its membership value, and returns a boolean indicating whether the element should be included in the new fuzzy set. The resulting fuzzy set contains only the elements that satisfy the condition.

Parameters:
  • condition (Callable) –

    A callable function that takes a tuple (element, membership) and returns True if the element should be included in the resulting set, False otherwise.

Raises:
  • AssertionError

    If condition is not a callable function.

Returns:
  • FuzzySet( FuzzySet ) –

    A new DiscreteFuzzySet object containing only the elements that satisfy the condition.

Examples:

>>> condition = lambda x: x[-1] > 0.6
>>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8})
>>> selected_set = original_set.select(condition)
>>> print(selected_set)
{('c', 'd'): 0.8}
Source code in pyPRUF/fuzzy_sets.py
def select(self, condition: Callable) -> FuzzySet: # NON TESTATO : Banale
    """Selects elements from the fuzzy set based on a condition.

    This method filters the fuzzy set by applying a specified condition to each element and its
    membership value. The condition is a callable that takes a tuple consisting of the element and
    its membership value, and returns a boolean indicating whether the element should be included
    in the new fuzzy set. The resulting fuzzy set contains only the elements that satisfy the condition.

    Args:
        condition (Callable): A callable function that takes a tuple
            (element, membership) and returns `True` if the element
            should be included in the resulting set, `False`
            otherwise.

    Raises:
        AssertionError: If `condition` is not a callable function.

    Returns:
        FuzzySet: A new `DiscreteFuzzySet` object containing only
            the elements that satisfy the condition.

    Examples:
        >>> condition = lambda x: x[-1] > 0.6
        >>> original_set = DiscreteFuzzySet(('x', 'y'), {('a', 'b'): 0.5, ('c', 'd'): 0.8})
        >>> selected_set = original_set.select(condition)
        >>> print(selected_set)
        {('c', 'd'): 0.8}
    """
    assert isinstance(condition, Callable), "'function' must be a callable function!"
    new_mf = {}
    for element, membership in self.__mf.items():
        if condition(tuple(list(element) + [membership])):
            new_mf[element] = membership
    return DiscreteFuzzySet(self.get_schema(), new_mf)

tab_str(n_cols=1, max_rows=10, padding=4)

Returns the tabular format string of this fuzzy relation.

Returns:
  • str( str ) –

    The tabular format string of this fuzzy relation.

Source code in pyPRUF/fuzzy_sets.py
def tab_str(self, n_cols = 1, max_rows=10, padding=4) -> str: # differentia
    """Returns the tabular format string of this
    fuzzy relation.

    Returns:
        str: The tabular format string of this
            fuzzy relation.
    """
    max_rows = min(len(self.__mf), max_rows)
    assert n_cols <= ceil(len(self.__mf) / max_rows), "'n_cols' exceeds the minimum number of columns necessary to describe this fuzzy set!"
    count = 0
    s = ''
    table = [()] * max_rows
    header = []
    cols = len(self.__schema) + 1
    for c in range(n_cols):
        header += self.__schema + ['mu']

    is_truncated = False
    for key, value in self.__mf.items():
        if count < max_rows * n_cols:
            table[count % max_rows] += key + (round(value, 2), )
            count += 1
        else:
            is_truncated = True
            break

    while count < max_rows * n_cols:
        table[count % max_rows] += ('/',) * cols
        count += 1

    col_widths = [max(len(str(item)) for item in col) + padding for col in zip(header, *table)]

    s = "".join(f"{attr:<{col_widths[i]}}" for i, attr in enumerate(header)) + "\n"
    for row in table:
        s += "".join(f"{str(cell):<{col_widths[i]}}" for i, cell in enumerate(row)) + "\n"
    if is_truncated:
        s += 'Output truncated: n. of rows = ' + str(len(self.__mf))
    return s

to_dictionary()

Returns a dictionary where in the (key, value) pair the key represents the element in the fuzzy relation and the value represents its membership degree.

Returns:
  • dict( dict ) –

    A dictionary representing the fuzzy relation.

Source code in pyPRUF/fuzzy_sets.py
def to_dictionary(self) -> dict: # differentia
    """Returns a dictionary where in the (key, value) pair
    the key represents the element in the fuzzy relation
    and the value represents its membership degree.

    Returns:
        dict: A dictionary representing the fuzzy relation.
    """
    return self.__mf.copy()

FuzzySet

Bases: ABC

Abstract Base Class for representing a fuzzy set.

A fuzzy set is a mathematical representation of a set where each element has a degree of membership ranging between 0 and 1. This class provides a framework for creating and managing fuzzy sets.

Subclasses of FuzzySet must implement the required methods defined by the abstract base class.

Source code in pyPRUF/fuzzy_sets.py
class FuzzySet(ABC):

    """Abstract Base Class for representing a fuzzy set.

    A fuzzy set is a mathematical representation of a set where each
    element has a degree of membership ranging between 0 and 1.
    This class provides a framework for creating and managing fuzzy sets.

    Subclasses of `FuzzySet` must implement the required methods defined by the abstract base class.
    """

    @abstractmethod
    def __getitem__(self, element) -> float: # membership: []
        pass

    @abstractmethod
    def __setitem__(self, element, membership: float) -> None: # add/update_element: []
        pass

    @abstractmethod
    def __delitem__(self, element) -> None: # del
        pass

    @abstractmethod
    def __or__(self, set2) -> FuzzySet: # union: |, |=
        pass

    @abstractmethod
    def __and__(self, set2) -> FuzzySet: # intersection: &, &=
        pass

    @abstractmethod
    def __invert__(self) -> FuzzySet: # complement: ~
        pass

    @abstractmethod
    def __mul__(self, set2) -> FuzzySet: # cartesian_product: *, *=
        pass

    @abstractmethod
    def __matmul__(self, set2) -> FuzzySet: # natural_join: @, @=
        pass

    @abstractmethod
    def __truediv__(self, set2) -> float: # proportion: /
        pass

    @abstractmethod
    def projection(self, subschema: tuple, operator: FuzzyBinaryOperator) -> FuzzySet:
        pass

    @abstractmethod
    def particularization(self, assignment: dict) -> FuzzySet: # particularization
        pass

    @abstractmethod
    def cardinality(self) -> float:
        pass

    @abstractmethod
    def mean_cardinality(self) -> float:
        pass

    @abstractmethod
    def compatibility(self, reference_set) -> FuzzySet:
        pass

    @abstractmethod
    def consistency(self, reference_set) -> float:
        pass

    @abstractmethod
    def get_schema(self) -> Tuple[str]:
        pass

    @abstractmethod
    def rename_schema(self, ren_dict: dict) -> None: # <<
        pass

    @abstractmethod
    def select(self, condition: Callable) -> FuzzySet:
        pass

    @abstractmethod
    def apply(self, operator: FuzzyUnaryOperator) -> FuzzySet:
        pass

    @abstractmethod  
    def extension_principle(self, function: Callable, out_schema: tuple) -> FuzzySet:
        pass

    @abstractmethod
    def cylindrical_extension(self, set2) -> Tuple[FuzzySet, FuzzySet]:
        pass

pyPRUF.membership_functions

Bell

Bases: MembershipFunction

A bell membership function for all those fuzzy sets with domain the set of real numbers.

Source code in pyPRUF/membership_functions.py
class Bell(MembershipFunction):
    """A bell membership function for all those fuzzy sets
    with domain the set of real numbers.
    """

    def __init__(self, m: float, s: float) -> None:
        """Initialize the bell-shaped membership function.

        Args:
            m (float): The center of the bell curve.
            s (float): The width of the bell curve.

        Raises:
            AssertionError: If the input values for `m` or `s` are not
                of type `float`.
        """
        # super().__init__()
        assert isinstance(m, float), "'a' must be a float!"
        assert isinstance(s, float), "'b' must be a float!"
        self.__m = m
        self.__s = s

    def __call__(self, x) -> float:
        """Calculate the membership degree for a given
        element `x` based on the bell-shaped function.

        Args:
            x (float): The element for which to calculate the membership
                degree.

        Returns:
            float: math.exp(-((x - self.__m) ** 2) / (self.__s ** 2))

        Raises:
            AssertionError: If `x` is not of type `float`.
        """
        assert isinstance(x, float), "'x' must be a float!"
        return math.exp(-((x - self.__m) ** 2) / (self.__s ** 2))

__call__(x)

Calculate the membership degree for a given element x based on the bell-shaped function.

Parameters:
  • x (float) –

    The element for which to calculate the membership degree.

Returns:
  • float( float ) –

    math.exp(-((x - self.__m) ** 2) / (self.__s ** 2))

Raises:
  • AssertionError

    If x is not of type float.

Source code in pyPRUF/membership_functions.py
def __call__(self, x) -> float:
    """Calculate the membership degree for a given
    element `x` based on the bell-shaped function.

    Args:
        x (float): The element for which to calculate the membership
            degree.

    Returns:
        float: math.exp(-((x - self.__m) ** 2) / (self.__s ** 2))

    Raises:
        AssertionError: If `x` is not of type `float`.
    """
    assert isinstance(x, float), "'x' must be a float!"
    return math.exp(-((x - self.__m) ** 2) / (self.__s ** 2))

__init__(m, s)

Initialize the bell-shaped membership function.

Parameters:
  • m (float) –

    The center of the bell curve.

  • s (float) –

    The width of the bell curve.

Raises:
  • AssertionError

    If the input values for m or s are not of type float.

Source code in pyPRUF/membership_functions.py
def __init__(self, m: float, s: float) -> None:
    """Initialize the bell-shaped membership function.

    Args:
        m (float): The center of the bell curve.
        s (float): The width of the bell curve.

    Raises:
        AssertionError: If the input values for `m` or `s` are not
            of type `float`.
    """
    # super().__init__()
    assert isinstance(m, float), "'a' must be a float!"
    assert isinstance(s, float), "'b' must be a float!"
    self.__m = m
    self.__s = s

MembershipFunction

Bases: ABC

Abstract base class for a membership function used in fuzzy logic.

Subclasses must implement the __call__ method, which defines the membership function for a given element x.

Source code in pyPRUF/membership_functions.py
class MembershipFunction(ABC):
    """Abstract base class for a membership function used
    in fuzzy logic.

    Subclasses must implement the `__call__`
    method, which defines the membership function
    for a given element `x`.
    """
    INF = sys.float_info.max

    @abstractmethod
    def __call__(self, x) -> float:
        """Calculate the degree of membership for a given input `x`.

        Args:
            x: The element for which to
                calculate the membership degree.

        Returns:
            float: The degree of membership for the
                element `x`, in the range [0, 1].
        """
        pass

__call__(x) abstractmethod

Calculate the degree of membership for a given input x.

Parameters:
  • x

    The element for which to calculate the membership degree.

Returns:
  • float( float ) –

    The degree of membership for the element x, in the range [0, 1].

Source code in pyPRUF/membership_functions.py
@abstractmethod
def __call__(self, x) -> float:
    """Calculate the degree of membership for a given input `x`.

    Args:
        x: The element for which to
            calculate the membership degree.

    Returns:
        float: The degree of membership for the
            element `x`, in the range [0, 1].
    """
    pass

Trapezoidal

Bases: MembershipFunction

A trapezoidal membership function for all those fuzzy sets with domain the set of real numbers.

Source code in pyPRUF/membership_functions.py
class Trapezoidal(MembershipFunction):
    """A trapezoidal membership function for all those fuzzy sets
    with domain the set of real numbers.
    """

    def __init__(self, a: float, b: float, c: float, d: float) -> None:
        """Initialize the trapezoidal membership function.

        Args:
            a (float): The left endpoint of the trapezoid.
            b (float): The start of the top of the trapezoid.
            c (float): The end of the top of the trapezoid.
            d (float): The right endpoint of the trapezoid.

        Raises:
            AssertionError: If the input values do not satisfy the
                conditions \n
                - `a < b or (a == -MembershipFunction.INF and a == b)`
                - `b < c`
                - `c < d or (c == MembershipFunction.INF and c == d)`
        """
        # super().__init__()
        assert isinstance(a, float), "'a' must be a float!"
        assert isinstance(b, float), "'b' must be a float!"
        assert isinstance(c, float), "'c' must be a float!"
        assert isinstance(d, float), "'c' must be a float!"
        assert a <= b, "The condition a <= b must be satisfied"
        assert b <= c, "The condition b <= c must be satisfied"
        assert c <= d, "The condition c <= d must be satisfied"
        # assert (a < b or (a == -MembershipFunction.INF and a == b)), "The condition a < b or (a == -MembershipFunction.INF and a == b) must be satisfied!"
        # assert b < c, "The condition b < c must be satisfied!" # se togli questo assert viene rappresentata correttamente anche la triangolare
        # assert (c < d or (c == MembershipFunction.INF and c == d)), "The condition (c < d or (c == -MembershipFunction.INF and c == d)) must be satisfied!"
        self.__a = a
        self.__b = b
        self.__c = c
        self.__d = d

    def __call__(self, x) -> float:
        """Calculate the membership degree for a given element `x` based on the trapezoidal function.

        Args:
            x (float): The element for which to calculate the membership
                degree.

        Returns:
            float: - 0 if x < a or x > d \n
                - (x - a) / (b - a) if a <= x < b
                - 1 if b <= x <= c
                - (d - x) / (d - c) if c < x <= d

        Raises:
            AssertionError: If `x` is not of type float.
        """
        assert isinstance(x, float), "'x' must be a float!"
        if self.__a <= x and x < self.__b: # IMPORTANTE: il minore stretto a x < self.__b evita che quando a = b = -Inf ci sia divisione per 0
            return (x - self.__a) / (self.__b - self.__a)
        elif self.__b <= x and x <= self.__c:
            return 1.0
        elif self.__c < x and x < self.__d: # IMPORTANTE: il minore stretto a self.__c < x evita che quando c = d = Inf ci sia divisione per 0
            return (self.__d - x) / (self.__d - self.__c)
        return 0.0

__call__(x)

Calculate the membership degree for a given element x based on the trapezoidal function.

Parameters:
  • x (float) –

    The element for which to calculate the membership degree.

Returns:
  • float( float ) –
    • 0 if x < a or x > d

    • (x - a) / (b - a) if a <= x < b

    • 1 if b <= x <= c
    • (d - x) / (d - c) if c < x <= d
Raises:
  • AssertionError

    If x is not of type float.

Source code in pyPRUF/membership_functions.py
def __call__(self, x) -> float:
    """Calculate the membership degree for a given element `x` based on the trapezoidal function.

    Args:
        x (float): The element for which to calculate the membership
            degree.

    Returns:
        float: - 0 if x < a or x > d \n
            - (x - a) / (b - a) if a <= x < b
            - 1 if b <= x <= c
            - (d - x) / (d - c) if c < x <= d

    Raises:
        AssertionError: If `x` is not of type float.
    """
    assert isinstance(x, float), "'x' must be a float!"
    if self.__a <= x and x < self.__b: # IMPORTANTE: il minore stretto a x < self.__b evita che quando a = b = -Inf ci sia divisione per 0
        return (x - self.__a) / (self.__b - self.__a)
    elif self.__b <= x and x <= self.__c:
        return 1.0
    elif self.__c < x and x < self.__d: # IMPORTANTE: il minore stretto a self.__c < x evita che quando c = d = Inf ci sia divisione per 0
        return (self.__d - x) / (self.__d - self.__c)
    return 0.0

__init__(a, b, c, d)

Initialize the trapezoidal membership function.

Parameters:
  • a (float) –

    The left endpoint of the trapezoid.

  • b (float) –

    The start of the top of the trapezoid.

  • c (float) –

    The end of the top of the trapezoid.

  • d (float) –

    The right endpoint of the trapezoid.

Raises:
  • AssertionError

    If the input values do not satisfy the conditions

    • a < b or (a == -MembershipFunction.INF and a == b)
    • b < c
    • c < d or (c == MembershipFunction.INF and c == d)
Source code in pyPRUF/membership_functions.py
def __init__(self, a: float, b: float, c: float, d: float) -> None:
    """Initialize the trapezoidal membership function.

    Args:
        a (float): The left endpoint of the trapezoid.
        b (float): The start of the top of the trapezoid.
        c (float): The end of the top of the trapezoid.
        d (float): The right endpoint of the trapezoid.

    Raises:
        AssertionError: If the input values do not satisfy the
            conditions \n
            - `a < b or (a == -MembershipFunction.INF and a == b)`
            - `b < c`
            - `c < d or (c == MembershipFunction.INF and c == d)`
    """
    # super().__init__()
    assert isinstance(a, float), "'a' must be a float!"
    assert isinstance(b, float), "'b' must be a float!"
    assert isinstance(c, float), "'c' must be a float!"
    assert isinstance(d, float), "'c' must be a float!"
    assert a <= b, "The condition a <= b must be satisfied"
    assert b <= c, "The condition b <= c must be satisfied"
    assert c <= d, "The condition c <= d must be satisfied"
    # assert (a < b or (a == -MembershipFunction.INF and a == b)), "The condition a < b or (a == -MembershipFunction.INF and a == b) must be satisfied!"
    # assert b < c, "The condition b < c must be satisfied!" # se togli questo assert viene rappresentata correttamente anche la triangolare
    # assert (c < d or (c == MembershipFunction.INF and c == d)), "The condition (c < d or (c == -MembershipFunction.INF and c == d)) must be satisfied!"
    self.__a = a
    self.__b = b
    self.__c = c
    self.__d = d

Triangular

Bases: MembershipFunction

A triangular membership function for all those fuzzy sets with domain the set of real numbers.

Source code in pyPRUF/membership_functions.py
class Triangular(MembershipFunction):
    """A triangular membership function for all those fuzzy sets
    with domain the set of real numbers.
    """

    def __init__(self, a: float, b: float, c: float) -> None:
        """Initialize the triangular membership function.

        Args:
            a (float): The left endpoint of the triangle.
            b (float): The peak of the triangle.
            c (float): The right endpoint of the triangle.

        Raises:
            AssertionError: If the input values
                do not satisfy the condition `a < b < c`.
        """
        # super().__init__()
        assert isinstance(a, float), "'a' must be a float!"
        assert isinstance(b, float), "'b' must be a float!"
        assert isinstance(c, float), "'c' must be a float!"
        assert a < b and b < c, "The condition a < b < c must be satisfied!"
        self.__a = a
        self.__b = b
        self.__c = c

    def __call__(self, x) -> float:
        """Calculate the membership degree for a given
        element x based on the triangular function.

        Args:
            x (float): The element for which to
                calculate the membership degree.

        Returns:
            float: - (x - self.__a) / (self.__b - self.__a) if self.__a <= x and x <= self.__b \n
                - (self.__c - x) / (self.__c - self.__b) if self.__b <= x and x <= self.__c \n
                - .0 otherwise \n

        Raises:
            AssertionError: If `x` is not of type `float`.
        """
        assert isinstance(x, float), "'x' must be a float!"
        if self.__a <= x and x <= self.__b:
            return (x - self.__a) / (self.__b - self.__a)
        elif self.__b <= x and x <= self.__c:
            return (self.__c - x) / (self.__c - self.__b)
        return 0.0

__call__(x)

Calculate the membership degree for a given element x based on the triangular function.

Parameters:
  • x (float) –

    The element for which to calculate the membership degree.

Returns:
  • float( float ) –
    • (x - self.__a) / (self.__b - self.__a) if self.__a <= x and x <= self.__b

    • (self.__c - x) / (self.__c - self.__b) if self.__b <= x and x <= self.__c

    • .0 otherwise

Raises:
  • AssertionError

    If x is not of type float.

Source code in pyPRUF/membership_functions.py
def __call__(self, x) -> float:
    """Calculate the membership degree for a given
    element x based on the triangular function.

    Args:
        x (float): The element for which to
            calculate the membership degree.

    Returns:
        float: - (x - self.__a) / (self.__b - self.__a) if self.__a <= x and x <= self.__b \n
            - (self.__c - x) / (self.__c - self.__b) if self.__b <= x and x <= self.__c \n
            - .0 otherwise \n

    Raises:
        AssertionError: If `x` is not of type `float`.
    """
    assert isinstance(x, float), "'x' must be a float!"
    if self.__a <= x and x <= self.__b:
        return (x - self.__a) / (self.__b - self.__a)
    elif self.__b <= x and x <= self.__c:
        return (self.__c - x) / (self.__c - self.__b)
    return 0.0

__init__(a, b, c)

Initialize the triangular membership function.

Parameters:
  • a (float) –

    The left endpoint of the triangle.

  • b (float) –

    The peak of the triangle.

  • c (float) –

    The right endpoint of the triangle.

Raises:
  • AssertionError

    If the input values do not satisfy the condition a < b < c.

Source code in pyPRUF/membership_functions.py
def __init__(self, a: float, b: float, c: float) -> None:
    """Initialize the triangular membership function.

    Args:
        a (float): The left endpoint of the triangle.
        b (float): The peak of the triangle.
        c (float): The right endpoint of the triangle.

    Raises:
        AssertionError: If the input values
            do not satisfy the condition `a < b < c`.
    """
    # super().__init__()
    assert isinstance(a, float), "'a' must be a float!"
    assert isinstance(b, float), "'b' must be a float!"
    assert isinstance(c, float), "'c' must be a float!"
    assert a < b and b < c, "The condition a < b < c must be satisfied!"
    self.__a = a
    self.__b = b
    self.__c = c