Fuzzifier: From Crisp to Fuzzy

In axisfuzzy, the fuzzification system is a cornerstone of the library, designed for flexibility and extensibility. It is orchestrated by the main Fuzzifier class, which acts as a high-level interface for converting crisp inputs (like numbers, lists, or NumPy arrays) into fuzzy representations such as Fuzznum and Fuzzarray.

This system is built on a modular architecture that leverages the Strategy Pattern. It allows users to dynamically select from various fuzzification algorithms, each encapsulated within a FuzzificationStrategy. These strategies are discovered and managed by a singleton FuzzificationStrategyRegistry, ensuring a consistent and extensible framework.

This document provides a comprehensive guide to the axisfuzzy fuzzifier, from its architectural principles to practical, hands-on examples.

Introduction to Fuzzification

What is Fuzzification?

Fuzzification is the process of transforming a crisp (i.e., precise, non-fuzzy) numerical value into a fuzzy set. It is the first and one of the most critical steps in any fuzzy logic system. While a crisp value represents a single point, a fuzzy set represents that value with a degree of membership over a range of possibilities, capturing the inherent ambiguity and imprecision of real-world concepts.

For example, instead of defining a “hot” temperature as a single value like 30°C, fuzzification allows us to represent it as a fuzzy set where different temperatures have varying degrees of “hotness.” A temperature of 28°C might have a membership degree of 0.8 to the “hot” set, while 25°C might have a degree of 0.4.

The Role of the Fuzzifier in axisfuzzy

In the axisfuzzy library, the Fuzzifier class serves as the central engine for this transformation. It encapsulates the logic, algorithms, and configurations required to convert crisp inputs—be it a single number, a list, or a NumPy array—into fuzzy numbers (Fuzznum) or fuzzy arrays (Fuzzarray).

The design of the Fuzzifier is guided by two core principles: flexibility and extensibility. It allows users to easily select different fuzzification strategies and membership functions, while also providing a clear path for developers to extend the system with custom logic.

Architectural Deep Dive

The fuzzification subsystem in axisfuzzy is built upon a robust and modular architecture composed of three key components. This design promotes separation of concerns and leverages powerful design patterns to create a system that is both easy to use and easy to extend.

The Core Components

  1. FuzzificationStrategy: This is an abstract base class (ABC) that defines the contract for all fuzzification algorithms. Any class that performs the actual conversion of a crisp value to a fuzzy number must inherit from this class and implement the abstract fuzzify method. This ensures a consistent interface across all strategies.

  2. FuzzificationStrategyRegistry: This is a singleton registry that discovers, stores, and manages all available FuzzificationStrategy implementations. It acts as a central lookup service, allowing the Fuzzifier to find the appropriate strategy based on the fuzzy number type (mtype) and a chosen method. The registry is populated automatically using the @register_fuzzifier decorator, making new strategies instantly available to the system upon definition.

  3. Fuzzifier: This is the primary user-facing class. It acts as a high-level orchestrator or a “facade” that coordinates the other components. When a user wants to perform fuzzification, they instantiate a Fuzzifier, providing it with a membership function and specifying the desired strategy. The Fuzzifier then consults the registry to find the correct strategy and delegates the actual fuzzification task to it.

To better visualize how these components interact, the following diagram illustrates the architectural flow:

Input: Crisp Value/Array
        |
        v
┌─────────────────────────────────────────────────────────────┐
│                    Fuzzifier                                │
│  ┌─────────────────┐    ┌─────────────────────────────────┐ │
│  │  Membership     │    │     Fuzzification Strategy      │ │
│  │  Function       │───▶│                                 │ │
│  │                 │    │                                 │ │
│  └─────────────────┘    └─────────────────────────────────┘ │
│                                    │                        │
└────────────────────────────────────┼────────────────────────┘
                                      v
                        Output: Fuzznum/Fuzzarray

Design Patterns in Action

The architecture elegantly employs two classic design patterns:

  • The Strategy Pattern: The relationship between the Fuzzifier and the various FuzzificationStrategy implementations is a textbook example of the Strategy Pattern. The Fuzzifier (the context) is configured with a concrete strategy object. When its fuzzify method is called, it delegates the call to the strategy object. This allows the algorithm used for fuzzification to be selected and swapped at runtime, completely decoupling the client (Fuzzifier) from the implementation of the algorithm (FuzzificationStrategy).

  • The Singleton Pattern (for the Registry): The FuzzificationStrategyRegistry is implemented as a singleton. This ensures that there is only one instance of the registry throughout the application’s lifecycle. A single, centralized registry guarantees that all parts of the system have a consistent view of the available strategies, preventing fragmentation and simplifying management. The global get_registry_fuzzify() function provides access to this single instance.

This combination of components and design patterns results in a highly cohesive and loosely coupled system, which is fundamental to the maintainability and scalability of axisfuzzy.

FuzzificationStrategy: The Blueprint for Fuzzification

At the heart of the fuzzification engine is the FuzzificationStrategy abstract base class (ABC). This class serves as a formal contract, or blueprint, for all fuzzification algorithms within axisfuzzy. It ensures that every strategy, regardless of its internal complexity, presents a consistent interface to the rest of the system, particularly to the Fuzzifier.

Core Responsibilities

The primary responsibilities of a FuzzificationStrategy are:

  1. Defining the Algorithm: It must implement the logic for converting a crisp numerical input (a single number or an array) into a fuzzy number (Fuzznum) or a fuzzy array (Fuzzarray).

  2. Interfacing with Membership Functions: It orchestrates the use of a MembershipFunction to calculate the membership degree(s) that form the basis of the resulting fuzzy number.

  3. Handling Fuzzy Number Specifics: It must correctly construct the target fuzzy number, respecting its mathematical constraints (e.g., for a QROFN, ensuring that \(\mu^q + \nu^q \le 1\)).

The fuzzify Method Contract

The central piece of the strategy is the abstract method fuzzify:

@abstractmethod
def fuzzify(self,
            x: Union[float, int, list, np.ndarray],
            mf_cls: type,
            mf_params_list: List[Dict]) -> Union[Fuzznum, Fuzzarray]:
    """
    Fuzzifies the input data.
    """
    pass

This method signature is the core contract:

  • x: The crisp input value(s). It can be a single scalar, a list, or a NumPy array, allowing for both single-value and vectorized operations.

  • mf_cls: The class of the membership function to be used (e.g., GaussianMF). The strategy is responsible for instantiating it.

  • mf_params_list: A list of parameter dictionaries. This allows a single fuzzification call to use multiple variations of the same membership function, which is essential for creating hesitant fuzzy numbers or handling complex scenarios.

  • Return Value: The method must return either a single Fuzznum instance (for scalar input) or a Fuzzarray (for array-like input).

Strategy Identification

Each concrete strategy class is uniquely identified by two class attributes:

  • mtype: A string indicating the type of fuzzy number it produces (e.g., 'qrofn', 'qrohfn').

  • method: A string naming the specific algorithm (e.g., 'linear', 'entropy', 'custom').

These attributes are not just labels; they are the keys used by the registry to store and retrieve the strategy, forming the foundation of the system’s automatic discovery mechanism.

FuzzificationStrategyRegistry: The Central Directory

The FuzzificationStrategyRegistry is the central nervous system of the fuzzification module. It acts as a comprehensive directory or catalog of all available FuzzificationStrategy implementations. Implemented as a singleton, it ensures that there is one, and only one, source of truth for strategy discovery throughout the application.

Core Responsibilities

  1. Strategy Registration: It provides a mechanism to register new strategy classes.

  2. Strategy Discovery and Retrieval: It allows the Fuzzifier to look up and retrieve the appropriate strategy class based on the desired fuzzy number type (mtype) and algorithm name (method).

  3. Default Management: It manages a default method for each fuzzy number type, simplifying the user experience.

The Automatic Registration and Discovery Mechanism

The most powerful feature of the registry is its seamless integration with the register_fuzzifier() decorator. This decorator automates the entire registration process.

from .registry import register_fuzzifier

@register_fuzzifier
class MyCustomStrategy(FuzzificationStrategy):
    # ... implementation ...

When Python loads this code, the decorator immediately calls the registry’s register method:

Key Methods of the Registry

The registry exposes several key methods for managing strategies:

  • register(mtype, method, strategy_cls, is_default=False): This is the core registration function, called automatically by the decorator. It maps the (mtype, method) tuple to the strategy_cls. If is_default is True, it sets this method as the default for the given mtype.

  • get_strategy(mtype, method=None): This is the primary lookup method used by the Fuzzifier. It retrieves the strategy class associated with the given mtype and method. If method is omitted, it intelligently falls back to the registered default method for that mtype. This allows users to simply specify a fuzzy number type (like 'qrofn') and get the most common or recommended fuzzification algorithm for it without needing to know the specific method name.

  • list_strategies(mtype=None): Provides a list of all registered (mtype, method) tuples, allowing for introspection and discovery of available capabilities.

  • get_available_mtypes() and get_available_methods(mtype): These helper methods make it easy to query which fuzzy number types are supported and what methods are available for each type.

Decoupling in Action

The registry, in combination with the strategy pattern, creates a beautifully decoupled architecture. The Fuzzifier does not need to know about any concrete strategy classes. It only communicates with the registry, asking for a strategy that matches the user’s request.

This design means that developers can add entirely new fuzzification algorithms to axisfuzzy without ever touching the core Fuzzifier code. As long as a new strategy adheres to the FuzzificationStrategy contract and is registered with the decorator, it instantly becomes available throughout the system.

Using the Fuzzifier

This section provides a practical, step-by-step guide to using the Fuzzifier. We will cover everything from the initial setup to creating a Fuzzifier instance, performing the actual fuzzification, and interpreting the results. The design of the Fuzzifier emphasizes flexibility, allowing you to configure it in several ways to best suit your application’s needs.

Creating a Fuzzifier Instance: Three Core Approaches

Instantiating a Fuzzifier is a declarative process. You specify what you want to achieve, and the Fuzzifier handles the how. The constructor’s flexibility shines in how you define the membership function (mf), which can be done in three distinct ways.

The general constructor signature is:

Fuzzifier(mf, mtype=None, method=None, **kwargs)

Key Parameters:

  • mf: The membership function definition. This can be an instance, a class, or a string name.

  • mtype: A string specifying the target fuzzy number type (e.g., 'qrofn', 'qrohfn').

  • method: A string specifying the fuzzification strategy. If omitted, the registered default for the mtype is used.

  • **kwargs: A flexible set of keyword arguments, including:

    • mf_params: Crucially, this dictionary (or list of dictionaries) provides the parameters for the membership function when mf is a class or a name.

    • Other parameters required by the chosen strategy (e.g., q for q-rung orthopair fuzzy numbers).

Let’s explore the three approaches to providing mf.

Approach 1: By Membership Function Instance (The Direct Approach)

This is the most direct method. You create and configure an instance of a MembershipFunction subclass first, and then pass it to the Fuzzifier. The Fuzzifier will automatically infer the parameters from the instance.

from axisfuzzy.fuzzifier import Fuzzifier
from axisfuzzy.membership import GaussianMF

# Step 1: Create a pre-configured membership function instance
mf_instance = GaussianMF(sigma=4.0, c=20.0)

# Step 2: Pass the instance to the Fuzzifier.
# Parameters are automatically inferred. No `mf_params` needed.
fuzz_engine = Fuzzifier(
    mf=mf_instance,
    mtype='qrofn',
    q=3
)

print(f"Engine created with instance: {fuzz_engine}")

Approach 2: By Membership Function Class (The Declarative Approach)

This approach is more declarative. You provide the membership function class itself (e.g., GaussianMF) and supply its parameters separately through the mf_params keyword argument. This keeps the configuration centralized in the Fuzzifier constructor.

from axisfuzzy.fuzzifier import Fuzzifier
from axisfuzzy.membership import GaussianMF

# Pass the class and its parameters separately
fuzz_engine = Fuzzifier(
    mf=GaussianMF,
    mtype='qrofn',
    q=3,
    mf_params={'sigma': 4.0, 'c': 20.0}  # Parameters for GaussianMF
)

print(f"Engine created with class: {fuzz_engine}")

Approach 3: By Membership Function Name (The Dynamic Approach)

This is the most flexible method, ideal for dynamic configurations (e.g., loading from a JSON or YAML file). You provide the registered string name or alias of the membership function (e.g., 'gaussmf') and its parameters via mf_params.

from axisfuzzy.fuzzifier import Fuzzifier

# Use the registered name 'gaussmf'
fuzz_engine = Fuzzifier(
    mf='gaussmf',
    mtype='qrofn',
    q=3,
    mf_params={'sigma': 4.0, 'c': 20.0}
)

print(f"Engine created with name: {fuzz_engine}")

This approach decouples your code from specific class imports, making it highly adaptable.

Performing Fuzzification

Once instantiated, the Fuzzifier object is callable, meaning you can use it like a function. It seamlessly handles single values, lists, or NumPy arrays, always returning the appropriate fuzzy representation.

import numpy as np

# Assuming `fuzz_engine` is one of the Fuzzifiers created above.

# a. Fuzzify a single crisp value
crisp_value = 18.0
fuzzy_number = fuzz_engine(crisp_value)

print(f"Crisp input: {crisp_value}")
print(f"Fuzzy output: {fuzzy_number}")
print(f"Type of output: {type(fuzzy_number)}")

# b. Fuzzify a NumPy array
crisp_array = np.array([15.0, 20.0, 25.0])
fuzzy_array = fuzz_engine(crisp_array)

print(f"\nCrisp array input: {crisp_array}")
print(f"Fuzzy array output: {fuzzy_array}")
print(f"Type of output: {type(fuzzy_array)}")

Interpreting the Result

The output of a fuzzification operation is either a Fuzznum (for a single input) or a Fuzzarray (for an array input).

A Fuzznum is a structured object that holds the membership degree (md), non-membership degree (nmd)(example for qrofn).

For our qrofn example, the fuzz_engine would perform the following internal steps when called with \(x = 18.0\):

  1. Calculate Membership Degree: It computes md = GaussianMF(sigma=4.0, c=20.0).compute(18.0).

  2. Apply Fuzzification Strategy: The chosen qrofn strategy then uses this md and the q=3 parameter to calculate the nmd. For a standard qrofn, this is typically nmd = (1 - md**q)**(1/q).

  3. Construct Fuzznum: It returns a Fuzznum object containing the calculated md and nmd.

The resulting fuzzy number encapsulates the input’s belongingness to the fuzzy set defined by the Gaussian curve, providing a rich, nuanced representation compared to the original crisp value.

Handling Complex Scenarios: Multiple Membership Functions

A powerful feature of the Fuzzifier is its ability to handle multiple membership function definitions for a single fuzzification task. This is particularly important for creating Hesitant Fuzzy Numbers (like qrohfn), where a single crisp value is mapped to multiple membership degrees, reflecting uncertainty or expert disagreement.

This is achieved by passing a list of dictionaries to the mf_params argument.

Use Case: Creating a Q-Rung Orthopair Hesitant Fuzzy Number (qrohfn)

Let’s configure a Fuzzifier to evaluate a score based on three different perspectives: pessimistic, neutral, and optimistic. Each perspective is represented by a different Gaussian membership function.

from axisfuzzy.fuzzifier import Fuzzifier

# Configure a Fuzzifier for a hesitant fuzzy scenario
hesitant_fuzzifier = Fuzzifier(
    mf='gaussmf',
    mtype='qrohfn',  # Target: q-rung Orthopair Hesitant Fuzzy Number
    q=2,
    mf_params=[
        {'sigma': 0.1, 'c': 0.3},  # Pessimistic view
        {'sigma': 0.1, 'c': 0.5},  # Neutral view
        {'sigma': 0.1, 'c': 0.7}   # Optimistic view
    ]
)

# Fuzzify a single score
score = 0.45
hesitant_fuzzy_number = hesitant_fuzzifier(score)

print(f"Crisp score: {score}")
print(f"Hesitant Fuzzy Number: {hesitant_fuzzy_number}")

Output

Crisp score: 0.45
Hesitant Fuzzy Number: <[0.0439, 0.3247, 0.8825],[0.1]>

When hesitant_fuzzifier is called, it computes the membership degree for the input score 0.45 against each of the three Gaussian functions, resulting in a Fuzznum that contains a set of membership degrees, capturing the full spectrum of the hesitant evaluation.

Note

Critical Difference: QROFN vs QROHFN Multi-Parameter Fuzzification

When using multiple membership function parameters (mf_params as a list), there is a fundamental difference between qrofn and qrohfn fuzzification strategies:

  • QROFN (Q-Rung Orthopair Fuzzy Numbers): Multiple parameters create separate fuzzy numbers that are stacked together, resulting in a Fuzzarray with an additional dimension. For example, if you fuzzify a 2D NumPy array with 3 parameter sets, the result will be a 3D Fuzzarray.

  • QROHFN (Q-Rung Orthopair Hesitant Fuzzy Numbers): Multiple parameters are fused into a single hesitant fuzzy number, maintaining the same dimensionality as the input. The same 2D array with 3 parameter sets results in a 2D Fuzzarray where each element contains multiple membership degrees.

This distinction is crucial for high-dimensional data processing and affects the shape of your output arrays. Choose the appropriate fuzzy number type based on whether you need separate evaluations (QROFN) or hesitant/uncertain evaluations (QROHFN).

Batch Processing of Array-Like Data

The Fuzzifier is optimized for performance and can seamlessly process not just single values, but also NumPy arrays and lists of numbers. When an array-like input is provided, the fuzzifier applies the same membership function(s) to every element in the array, returning a list of Fuzznum objects.

This vectorization-like capability is highly efficient for batch processing tasks, such as fuzzifying an entire dataset column.

import numpy as np
from axisfuzzy.fuzzifier import Fuzzifier

# Fuzzifier for 'medium' values
fuzzifier = Fuzzifier(mf='gaussmf', mf_params={'sigma': 0.1, 'c': 0.5})

# A dataset of crisp values
crisp_data = np.array([0.1, 0.48, 0.52, 0.9, 0.3])

# Fuzzify the entire array in one go
fuzzy_results = fuzzifier(crisp_data)

# The output is a list of Fuzznum objects
for crisp, fuzzy in zip(crisp_data, fuzzy_results):
    print(f"Crisp: {crisp:.2f} -> Fuzzy: {fuzzy}")

This demonstrates how easily the Fuzzifier scales from single data points to large datasets, making it a powerful tool for data analysis and feature engineering pipelines.

Advanced Features

This section delves into the advanced capabilities of the Fuzzifier, specifically its support for serialization and visualization. These features are designed to enhance portability, reproducibility, and analytical insight.

Serialization for Portability

A key feature of the Fuzzifier is its ability to be serialized. This means you can save the complete configuration of a Fuzzifier instance—including its membership function, strategy, and all associated parameters—and then perfectly reconstruct it later. This is invaluable for model persistence, sharing configurations, and ensuring reproducibility in experiments.

This functionality is provided by two paired methods: get_config() and from_config().

  • get_config(): This instance method returns a serializable dictionary containing all the information required to recreate the Fuzzifier.

  • from_config(): This class method takes a configuration dictionary (generated by get_config) and returns a new Fuzzifier instance with the exact same configuration.

Practical Use Case: Model Persistence and Sharing

Imagine you have configured a complex Fuzzifier for a specific data analysis task. You can save its configuration to a file (e.g., in JSON format) and load it back whenever needed, without having to manually redefine it in your code.

import json
from axisfuzzy.fuzzifier import Fuzzifier

# 1. Create and configure a Fuzzifier
original_fuzzifier = Fuzzifier(
    mf='gaussmf',
    mtype='qrofn',
    q=2,
    mf_params={'sigma': 0.5, 'c': 0.5}
)

# 2. Get its configuration and save it to a file
config = original_fuzzifier.get_config()

with open('fuzzifier_config.json', 'w') as f:
    json.dump(config, f)

# ... later, in a different script or session ...

# 3. Load the configuration from the file
with open('fuzzifier_config.json', 'r') as f:
    loaded_config = json.load(f)

# 4. Reconstruct the Fuzzifier from the configuration
reconstructed_fuzzifier = Fuzzifier.from_config(loaded_config)

# The reconstructed Fuzzifier is identical to the original
print(f"Original: {original_fuzzifier}")
print(f"Reconstructed: {reconstructed_fuzzifier}")

# Verify that they produce the same output
crisp_value = 0.7
assert original_fuzzifier(crisp_value) == reconstructed_fuzzifier(crisp_value)

Visualization for Fuzzifier

Understanding the shape and position of the membership function(s) is crucial for interpreting the behavior of your fuzzy system. The Fuzzifier provides a built-in plot() method for this exact purpose.

This method visualizes the membership function(s) associated with the Fuzzifier instance over a specified range.

Using the plot() Method

The plot() method is straightforward to use. You can control the x-axis range and the number of points used for plotting.

from axisfuzzy.fuzzifier import Fuzzifier

# Create a Fuzzifier with multiple membership functions
# for a hesitant fuzzy scenario
hesitant_fuzzifier = Fuzzifier(
    mf='gaussmf',
    mtype='qrohfn',
    q=2,
    mf_params=[
        {'sigma': 0.1, 'c': 0.3},
        {'sigma': 0.05, 'c': 0.6},
        {'sigma': 0.1, 'c': 0.4}
    ]
)

# Visualize the underlying membership functions
hesitant_fuzzifier.plot(
    x_range=(0, 1),
    num_points=200,
    show=True  # Set to False if you want to customize the plot further
)
Visualization of the Fuzzifier

This will generate a plot showing the three Gaussian curves, allowing you to instantly see how they overlap and cover the input space.

Customizing Plots

The plot() method is built on top of matplotlib. While it provides a quick way to generate a standard plot, you can easily create more advanced and customized visualizations. By setting show=False, the plot is not immediately displayed, which allows you to access the current matplotlib figure and axes to add custom titles, labels, annotations, or other elements before finally showing it yourself.

Extending the System: Custom Fuzzification Strategies

The axisfuzzy fuzzification engine is designed for extensibility. While it comes with a set of standard, built-in strategies, its true power lies in allowing users to define and integrate their own custom fuzzification logic. This section guides you through the process of creating and registering your own FuzzificationStrategy.

When to Build Your Own Strategy?

You should consider creating a custom strategy in any of the following scenarios:

  • Novel Fuzzy Number Types: If you are implementing a new type of fuzzy number that is not already supported by axisfuzzy, you will need to create a strategy to handle its specific membership and non-membership calculations.

  • Alternative Fuzzification Logic: You may have a theoretical model or a specific application requirement that defines the relationship between membership (md) and non-membership (nmd) differently from the standard strategies. For example, you might want nmd to be a function of both md and some external parameter.

  • Domain-Specific Adjustments: In certain expert systems, the fuzzification process might need to incorporate domain-specific rules or heuristics that go beyond a simple mathematical transformation.

  • Performance Optimization: For performance-critical applications, you might design a highly optimized strategy that is tailored to a specific hardware architecture or numerical library.

A Step-by-Step Implementation Guide

Creating and integrating a new strategy is a straightforward process involving three key steps, as illustrated by the built-in strategies for qrofn and qrohfn.

1. Define the Strategy Class

First, create a new Python class that inherits from the abstract base class FuzzificationStrategy. Inside the class, you must define two class attributes:

  • mtype: A string that specifies the fuzzy number type this strategy is for (e.g., ‘qrofn’, ‘qrohfn’).

  • method: A string that provides a unique name for this strategy within its mtype (e.g., ‘default’, ‘pessimistic’).

from axisfuzzy.fuzzifier.strategy import FuzzificationStrategy

class MyCustomStrategy(FuzzificationStrategy):
    mtype = 'qrofn'  # Target fuzzy number type
    method = 'my_special_method' # Unique name for the strategy

    # ... implementation will go here ...

2. Implement the `fuzzify` Abstract Method

Next, you must implement the fuzzify method. This is the core of your strategy, containing the logic to convert a crisp input into a fuzzy number or an array of fuzzy numbers.

The correct signature is:

from typing import Union, List, Dict
import numpy as np
from axisfuzzy.core import Fuzznum, Fuzzarray

def fuzzify(self,
            x: Union[float, int, list, np.ndarray],
            mf_cls: type,
            mf_params_list: List[Dict]) -> Union[Fuzznum, Fuzzarray]:
    # Your custom logic here
    # This method should process the crisp input `x` using the provided
    # membership function class (`mf_cls`) and its parameters (`mf_params_list`)
    # and return a Fuzznum or Fuzzarray.
    pass

Key parameters:

  • x: The crisp input value(s), which can be a single number, a list, or a NumPy array.

  • mf_cls: The membership function class (e.g., Gaussmf) to be used for calculating membership degrees.

  • mf_params_list: A list of dictionaries, where each dictionary contains the parameters for one membership function instance (e.g., [{'c': 0.5, 'sigma': 0.1}]).

3. Register the New Strategy

Finally, to make your strategy discoverable by the Fuzzifier, you must register it using the register_fuzzifier() decorator. This decorator reads the mtype and method attributes from your class to add it to the central registry.

The decorator can take one optional argument:

  • is_default (optional, bool): If True, this strategy will become the default method for the specified mtype. This means it will be used when a user requests that mtype without specifying a method.

from axisfuzzy.fuzzifier import register_fuzzifier

@register_fuzzifier(is_default=False)
class MyCustomStrategy(FuzzificationStrategy):
    mtype = 'qrofn'
    method = 'my_special_method'

    def fuzzify(self, x, mf_cls, mf_params_list):
        # ... implementation ...
        pass

A Complete Example: The “Pessimistic” QROFN Strategy

Let’s create a complete, practical example. We will implement a “pessimistic” strategy for q-rung Orthopair Fuzzy Numbers (qrofn). In this strategy, we’ll define the non-membership degree (nmd) to be slightly higher than in the standard approach, reflecting a more cautious or “pessimistic” evaluation.

Let’s say our pessimistic nmd is defined as \((1 - md^q)^{1/q} + 0.1 * (1 - md)\), but capped at \((1 - md^q)^(1/q)\).

import numpy as np
from typing import Union, List, Dict

from axisfuzzy.core import Fuzznum, Fuzzarray, get_registry_fuzztype
from axisfuzzy.fuzzifier import FuzzificationStrategy, register_fuzzifier, Fuzzifier

# Step 1 & 3: Define and Register the Strategy
@register_fuzzifier
class PessimisticQROFNStrategy(FuzzificationStrategy):
    """
    A pessimistic fuzzification strategy for Q-Rung Orthopair Fuzzy Numbers (QROFNs).

    This strategy calculates a non-membership degree that is intentionally
    higher than the standard, reflecting a more cautious or "pessimistic"
    evaluation. It demonstrates how to inject custom logic into the
    fuzzification process.
    """
    mtype = 'qrofn'
    method = 'pessimistic'

    # The __init__ method is inherited from FuzzificationStrategy,
    # which already handles the 'q' parameter.

    def fuzzify(self,
                x: Union[float, int, list, np.ndarray],
                mf_cls: type,
                mf_params_list: List[Dict]) -> Union[Fuzznum, Fuzzarray]:
        """
        Implements the pessimistic fuzzification logic.
        """
        # For a simple QROFN, we usually work with a single membership function.
        # We'll extract the parameters for the first one.
        params = mf_params_list[0]
        mf = mf_cls(**params)
        x = np.asarray(x, dtype=float)

        # 1. Calculate the membership degree (md) from the crisp input.
        md = np.clip(mf.compute(x), 0, 1)

        # 2. Implement the custom "pessimistic" logic for non-membership (nmd).
        # The standard nmd is the maximum possible value given md.
        standard_nmd = (1 - md**self.q)**(1/self.q)

        # Our pessimistic adjustment slightly increases the non-membership degree.
        pessimistic_nmd = standard_nmd + 0.1 * (1 - md)

        # We must still respect the QROFN constraint: md^q + nmd^q <= 1.
        # So, we take the minimum of our adjusted value and the maximum allowed value.
        nmd = np.minimum(pessimistic_nmd, (1 - md**self.q)**(1/self.q))

        # 3. Create the final fuzzy number(s) using the backend system.
        backend_cls = get_registry_fuzztype().get_backend(self.mtype)
        backend = backend_cls.from_arrays(mds=md, nmds=nmd, q=self.q)
        arr = Fuzzarray(backend=backend, mtype=self.mtype, q=self.q)

        # Return a single Fuzznum if the input was a scalar.
        if x.ndim == 0:
            return arr[()]
        return arr

Now, let’s use our new strategy

# Create a Fuzzifier and specify our custom method
pessimistic_fuzzifier = Fuzzifier(
    mf='gaussmf',
    mtype='qrofn',
    method='pessimistic',  # <-- Here we select our new strategy
    q=3,
    mf_params={'sigma': 4.0, 'c': 20.0}
)

# For comparison, create a Fuzzifier using the default strategy
default_fuzzifier = Fuzzifier(
    mf='gaussmf',
    mtype='qrofn',
    method='default',      # <-- The standard, built-in strategy
    q=3,
    mf_params={'sigma': 4.0, 'c': 20.0}
)

crisp_value = 18.0
pessimistic_result = pessimistic_fuzzifier(crisp_value)
default_result = default_fuzzifier(crisp_value)

print(f"Crisp Input: {crisp_value}\n")
print(f"Default Strategy Output:     {default_result}")
print(f"Pessimistic Strategy Output: {pessimistic_result}")
print(f"\nNote how the 'pessimistic' result has a slightly higher non-membership degree (nmd).")

output:

Crisp Input: 18.0

Default Strategy Output:     <0.8825,0.678>
Pessimistic Strategy Output: <0.8825,0.6788>

Note how the 'pessimistic' result has a slightly higher non-membership degree (nmd).

By following this pattern, you can seamlessly extend axisfuzzy with powerful, domain-specific fuzzification logic, making the system adaptable to virtually any research or application need.

Conclusion

The axisfuzzy fuzzification system stands as a powerful and flexible engine for transforming crisp data into rich, nuanced fuzzy representations. Its design, rooted in the Strategy Pattern, provides a robust framework that is both easy to use for standard applications and highly extensible for advanced, domain-specific research.

Through this guide, we have explored the system from multiple perspectives:

  • Architectural Soundness: The clear separation of concerns between the Fuzzifier, FuzzificationStrategy, and the FuzzificationStrategyRegistry creates a maintainable and scalable system.

  • Practical Application: We have demonstrated how to instantiate and use the Fuzzifier for converting single values and arrays into Fuzznum and Fuzzarray objects.

  • Advanced Capabilities: Features like serialization (get_config/ from_config) and visualization (plot) enhance model persistence, reproducibility, and analytical insight.

  • Unlimited Extensibility: The ability to create and register custom fuzzification strategies ensures that axisfuzzy can evolve to meet the unique demands of any fuzzy logic application or theoretical model.