Extension and Mixin Systems: Extending AxisFuzzy Functionality

AxisFuzzy provides two complementary systems for extending the functionality of Fuzznum and Fuzzarray objects: the Extension System and the Mixin Operations. These systems enable developers to add new capabilities without modifying the core library code, following different architectural patterns optimized for distinct use cases.

This guide explains when and how to use each system, providing practical examples and clear decision criteria to help you choose the right approach for your needs.

Overview of the Dual Architecture

The two systems serve complementary purposes:

  • Extension System: Provides type-aware functionality where behavior depends on the specific fuzzy number type (mtype). Examples include distance calculations, similarity measures, and scoring functions that vary between qrofn, qrohfn, and other types.

  • Mixin Operations: Provides type-agnostic structural operations that work uniformly across all fuzzy number types. Examples include array reshaping, transposition, and concatenation operations.

System Architecture Diagram

AxisFuzzy Extension Architecture
================================

┌─────────────────────────────────────────────────────────────────┐
│                    User Interface Layer                         │
│  Fuzznum.distance()  │  Fuzzarray.reshape()  │  af.similarity() │
└─────────────────────┬───────────────────────┬───────────────────┘
                      │                       │
┌─────────────────────▼───────────────────────▼───────────────────┐
│                Extension System             │  Mixin Operations │
│  ┌──────────────────────────────────────┐   │  ┌─────────────┐  │
│  │        Runtime Dispatch              │   │  │   Static    │  │
│  │  ┌──────────────────────────────┐    │   │  │  Injection  │  │
│  │  │    ExtensionDispatcher       │    │   │  │             │  │
│  │  │   ┌─────────────────────┐    │    │   │  │ ┌─────────┐ │  │
│  │  │   │  mtype='qrofs'      │    │    │   │  │ │ Factory │ │  │
│  │  │   │  → qrofs_distance() │    │    │   │  │ │Functions│ │  │
│  │  │   └─────────────────────┘    │    │   │  │ └─────────┘ │  │
│  │  │   ┌─────────────────────┐    │    │   │  │             │  │
│  │  │   │  mtype='qrohfs'     │    │    │   │  │ ┌─────────┐ │  │
│  │  │   │  → qrohfs_distance()│    │    │   │  │ │Registry │ │  │
│  │  │   └─────────────────────┘    │    │   │  │ │ System  │ │  │
│  │  │   ┌──────────────────────┐   │    │   │  │ └─────────┘ │  │
│  │  │   │  default=True        │   │    │   │  └─────────────┘  │
│  │  │   │  → default_distance()│   │    │   │                   │
│  │  │   └──────────────────────┘   │    │   │                   │
│  │  └──────────────────────────────┘    │   │                   │
│  └──────────────────────────────────────┘   │                   │
└─────────────────────────────────────────────┴───────────────────┘
                      │                       │
┌─────────────────────▼───────────────────────▼───────────────────┐
│                    Core Data Layer                              │
│        Fuzznum        │        Fuzzarray        │    Backend    │
│    (Scalar Fuzzy)     │    (Array of Fuzzy)     │      SoA      │
└─────────────────────────────────────────────────────────────────┘

Target Audience and Usage

Extension System - For External Users and Developers:

  • Primary audience: External users, researchers, and third-party developers

  • Purpose: Add custom fuzzy operations and mathematical functions

  • Use cases: Domain-specific fuzzy logic operations, custom distance metrics, specialized aggregation functions

  • Accessibility: Public API designed for ease of use

Mixin Operations - For AxisFuzzy Maintainers and Core Developers:

  • Primary audience: AxisFuzzy core maintainers and internal developers

  • Purpose: Implement universal structural operations and container behaviors

  • Use cases: Array manipulation, data structure operations, NumPy-like functionality

  • Accessibility: Internal API for framework development (static integration, not user-extensible)

Note

For most users: You will primarily use the Extension System to add custom functionality. The Mixin Operations is documented here for completeness and for those contributing to AxisFuzzy’s core. The Mixin system is not user-extensible. It provides a static set of operations that are integrated into the framework during initialization. Users cannot dynamically register new mixin functions.

# Extension System - for external users
@extension(name='custom_distance', mtype='qrofn')
def my_distance_metric(x, y, p=2):
    """Custom distance metric for Q-rung orthopair fuzzy numbers."""
    return ((abs(x.md**x.q - y.md**y.q)**p +
            abs(x.nmd**x.q - y.nmd**y.q)**p) / 2)**(1/p)

# Mixin Operations - for core developers (internal use)
# These are statically integrated during framework initialization
@register_mixin('reshape', target_classes=['Fuzzarray'])
def reshape_impl(self, *shape):
    """Internal implementation of reshape operation."""
    return _reshape_factory(self, *shape)

Extension System: Dynamic, Type-Aware Function Registration

The extension system is the cornerstone of AxisFuzzy’s dynamic functionality, designed to address operations whose logic is intrinsically tied to the mathematical definition of a fuzzy number type (mtype). It allows developers to register multiple implementations for a single function name, with the framework automatically dispatching to the correct one at runtime based on the object’s type. This polymorphic behavior is essential for building a robust and extensible fuzzy logic ecosystem.

Note

External Extension Support: AxisFuzzy v0.2.0+ includes enhanced support for external extensions. External users can now easily register custom extensions that are immediately available for use without manual intervention. See External Extension Registration: Simplified API for External Users for details.

Architectural Pillars

The power of the extension system stems from a clean, decoupled architecture comprising three pillars:

  1. The @extension Decorator: A declarative API for registering functions.

  2. The Extension Registry: A central, thread-safe registry that indexes all registered implementations and their metadata.

  3. The Dynamic Injector: A mechanism that injects the registered functions as methods or properties into target classes (like Fuzznum and Fuzzarray) at runtime.

Declarative Registration

Registering a type-specific implementation is achieved declaratively using the @extension decorator. This approach cleanly separates the core logic of your function from the registration process.

from axisfuzzy.extension import extension

@extension(name='similarity', mtype='qrofn')
def qrofn_similarity(x, y):
    """Cosine similarity for q-rung orthopair fuzzy numbers."""
    numerator = x.md * y.md + x.nmd * y.nmd
    denominator = ((x.md**2 + x.nmd**2) * (y.md**2 + y.nmd**2))**0.5
    return numerator / denominator if denominator > 0 else 0

@extension(name='similarity', mtype='qrohfn')
def qrohfn_similarity(x, y):
    """Similarity for q-rung orthopair hesitant fuzzy numbers."""
    # Different implementation for hesitant fuzzy numbers
    return calculate_hesitant_similarity(x, y)

Specializing by Fuzzy Type (mtype)

The primary strength of the extension system is its ability to specialize behavior based on mtype. Below are examples demonstrating how to provide distinct implementations for various fuzzy set types.

Q-rung Orthopair Fuzzy Numbers (qrofn)

@extension(name='distance', mtype='qrofn')
def qrofn_distance(x, y, p=2):
    """Calculate distance between two Q-rung orthopair fuzzy numbers."""
    return ((abs(x.md**x.q - y.md**y.q)**p +
            abs(x.nmd**x.q - y.nmd**y.q)**p) / 2)**(1/p)

Q-rung Orthopair Hesitant Fuzzy Numbers (qrohfn)

@extension(name='aggregation', mtype='qrohfn')
def qrohfn_aggregation(x, weights=None):
    """Aggregate Q-rung orthopair hesitant fuzzy values."""
    # Implementation for QROHFN aggregation
    return aggregate_hesitant_values(x, weights)

Classical Fuzzy Sets (fs)

@extension(name='defuzzify', mtype='fs')
def fs_defuzzify(x, method='centroid'):
    """Defuzzify a classical fuzzy set."""
    # Implementation for defuzzification
    return defuzzification_result(x, method)

Controlling API Exposure: Injection Types

The extension system provides fine-grained control over how a function is exposed to the end-user through the injection_type parameter. This flexibility allows for crafting intuitive and consistent APIs.

Instance Methods (default): Functions are added as methods to instances

@extension(name='normalize', mtype='qrofn', injection_type='instance_method')
def normalize_qrofn(self):
    """Normalize the Q-rung orthopair fuzzy number."""
    # Access instance data via self
    total = self.md + self.nmd
    if total > 0:
        return af.fuzzynum(self.md/total, self.nmd/total, mtype=self.mtype)
    return self

# Usage
qrofn_value = af.fuzzynum(md=0.8, nmd=0.3, mtype='qrofn', q=3)
normalized = qrofn_value.normalize()  # Called as instance method

Top-level Functions: Functions are available as standalone functions:

@extension(name='distance', mtype='qrofn', injection_type='top_level_function')
def qrofn_distance(a, b, metric='euclidean'):
    """Calculate distance between two Q-rung orthopair fuzzy numbers."""
    # Implementation
    return distance_value

# Usage
import axisfuzzy as af
dist = af.distance(qrofn1, qrofn2)  # Called as top-level function

Both Types: Functions are available both ways

@extension(name='complement', mtype='qrofn', injection_type='both')
def qrofn_complement(self):
    """Calculate the complement of a Q-rung orthopair fuzzy number."""
    # Implementation
    return af.fuzzynum(self.nmd, self.md, mtype=self.mtype, q=self.q)

# Usage - both ways work
comp1 = qrofn_value.complement()     # Instance method
comp2 = af.complement(qrofn_value)   # Top-level function

Instance Properties: Functions can be exposed as properties

@extension(name='score', mtype='qrofn', injection_type='instance_property')
def qrofn_score(self):
    """Calculate the score of a Q-rung orthopair fuzzy number."""
    return self.md**self.q - self.nmd**self.q

# Usage
score = qrofn_value.score  # Accessed as property (no parentheses)

The @extension Decorator API

The @extension decorator is the primary interface for registration, offering a rich set of parameters to precisely control a function’s behavior and metadata.

  • name (str): The function name that will be available on objects

  • mtype (str, optional): Target fuzzy number type (e.g., 'qrofn', 'qrohfn')

  • target_classes (list, optional): Classes to inject into Fuzznum, Fuzzarray or [Fuzznum, Fuzzarray]

  • injection_type (str): How the function is exposed:

    • 'instance_method' : Available as obj.function()

    • 'top_level_function' : Available as axisfuzzy.function()

    • 'both' : Available in both ways (default)

    • 'instance_property' : Available as obj.property

  • is_default (bool): Whether this is a fallback implementation

  • priority (int): Resolution priority for conflicting registrations

Seamless Dispatch in Action

Once an extension is registered, the framework’s dynamic dispatcher handles the rest. Calls made via instance methods or top-level functions are automatically routed to the appropriate implementation based on the object’s mtype, making the process transparent to the user.

import axisfuzzy as af

# Create fuzzy numbers
x = af.fuzzynum(md=0.8, nmd=0.3, mtype='qrofn', q=2)
y = af.fuzzynum(md=0.6, nmd=0.5, mtype='qrofn', q=2)

# Use as instance method
sim = x.similarity(y)

# Use as top-level function
sim = af.similarity(x, y)

# Both calls automatically dispatch to qrofn_similarity

Defining Fallback Behavior: Default Implementations

To enhance robustness, you can provide a generic implementation that serves as a fallback when no mtype-specific version is found. This is achieved by setting the is_default=True flag.

@extension(name='normalize', is_default=True)
def default_normalize(x):
    """Default normalization for any fuzzy number type."""
    total = x.md + x.nmd
    if total > 0:
        return af.fuzzynum(x.md/total, x.nmd/total, mtype=x.mtype)
    return x

Advanced Capabilities

Beyond basic registration, the extension system offers advanced features for managing complex scenarios, such as plugin architectures and conditional logic.

Conflict Resolution with Priority

In modular systems, it’s possible for multiple libraries to register an implementation for the same (name, mtype) pair. The priority parameter resolves such conflicts deterministically: the implementation with the highest priority wins. This prevents accidental overwrites and ensures predictable behavior.

@extension(name='distance', mtype='qrofn', priority=1)
def euclidean_distance(x, y):
    """Standard Euclidean distance."""
    return standard_euclidean(x, y)

@extension(name='distance', mtype='qrofn', priority=2)  # Higher priority
def improved_distance(x, y):
    """Improved distance calculation."""
    return improved_euclidean(x, y)  # This will be used

Conditional Registration

Registration can be guarded by conditional logic, allowing you to create extensions that depend on optional dependencies, such as NumPy.

# Only register if NumPy is available
try:
    import numpy as np

    @extension(name='to_numpy', mtype='qrofn')
    def qrofn_to_numpy(self):
        """Convert to NumPy array representation."""
        return np.array([self.md, self.nmd, self.q])
except ImportError:
    pass

Built-in Extensions

AxisFuzzy ships with a rich set of pre-registered extensions for common fuzzy number types, providing out-of-the-box functionality for a wide range of tasks.

For qrofn (q-Rung Orthopair Fuzzy Numbers):

  • Constructors: empty, positive, negative, full, empty_like, positive_like, negative_like, full_like

  • I/O Operations: to_csv, read_csv, to_json, read_json, to_npy, read_npy

  • Measurement: distance

  • String Conversion: str2fuzznum

  • Aggregation: sum, mean, max, min, prod, var, std

  • Instance Properties: score, acc, ind

For qrohfn (q-Rung Orthopair Hesitant Fuzzy Numbers):

  • Constructors: empty, positive, negative, full, empty_like, positive_like, negative_like, full_like

  • I/O Operations: to_csv, read_csv, to_json, read_json, to_npy, read_npy

  • Measurement: distance, normalize

  • String Conversion: str2fuzznum

  • Aggregation: sum, mean, max, min, prod, var, std

  • Instance Properties: score, acc, ind

Development Best Practices

To ensure your extensions are robust, maintainable, and integrate seamlessly with the AxisFuzzy ecosystem, adhere to the following best practices.

1. Adopt Clear Naming Conventions

# Good: descriptive and specific
@extension(name='cosine_similarity', mtype='qrofn')
def qrofn_cosine_similarity(x, y):
    pass

# Avoid: generic or ambiguous names
@extension(name='calc', mtype='qrofn')
def some_calculation(x, y):
    pass

2. Write Comprehensive Documentation

@extension(name='weighted_distance', mtype='qrofn')
def qrofn_weighted_distance(x, y, weights=None, p=2):
    """Calculate weighted Minkowski distance between Q-rung orthopair fuzzy numbers.

    Parameters
    ----------
    x, y : Fuzznum
        Q-rung orthopair fuzzy numbers to compare
    weights : array-like, optional
        Weights for membership and non-membership degrees
    p : float, default=2
        Minkowski distance parameter (p=2 for Euclidean)

    Returns
    -------
    float
        Weighted distance value
    """
    if weights is None:
        weights = [0.5, 0.5]
    # Implementation here
    pass

3. Leverage Backend-Based High-Performance Computing

For optimal performance, especially when working with Fuzzarray objects, design your extensions to leverage the underlying Struct of Arrays (SoA) architecture provided by FuzzarrayBackend. This approach ensures:

  • Memory Locality: Operations on component arrays (e.g., membership degrees) benefit from contiguous memory layout

  • Vectorization: NumPy-based operations can utilize SIMD instructions for parallel computation

  • Cache Efficiency: Reduced memory fragmentation leads to better CPU cache utilization

@extension(name='batch_operation', mtype='qrofn')
def qrofn_batch_operation(fuzz_array):
    """Example of backend-aware high-performance extension."""
    # Access backend directly for vectorized operations
    backend = fuzz_array._backend

    # Perform vectorized computation on component arrays
    result_mds = np.sqrt(backend.mds)  # Vectorized operation
    result_nmds = np.sqrt(backend.nmds)

    # Create new backend with results (fast path)
    from axisfuzzy.fuzztype.qrofs import QROFNBackend
    new_backend = QROFNBackend.from_arrays(
        mds=result_mds, nmds=result_nmds, q=backend.q
    )

    # Return new Fuzzarray using the fast path (O(1) operation)
    from axisfuzzy.core import Fuzzarray
    return Fuzzarray(backend=new_backend)

This pattern follows the same high-performance principles used throughout AxisFuzzy’s core, ensuring your extensions scale efficiently with large datasets.

Mixin Operations: Universal Structural Operations

The Mixin Operations offers a suite of universal, NumPy-inspired structural operations that are seamlessly integrated across all fuzzy data types within the AxisFuzzy ecosystem. These operations are designed for data manipulation and structural transformation, rather than fuzzy-specific arithmetic, providing a consistent and predictable API.

Core Philosophy

The design of the Mixin Operations is guided by a distinct set of principles compared to the Extension System:

  • Universality: Implement functions that are logically applicable to any fuzzy type, ensuring consistent behavior.

  • Structural Focus: Prioritize operations on the data container (e.g., shape, size, layout) over the fuzzy values themselves.

  • NumPy-like Interface: Adopt familiar and powerful array manipulation patterns from NumPy to lower the learning curve.

  • Composition over Inheritance: Dynamically compose mixins into core classes, promoting flexibility and avoiding rigid class hierarchies.

Demonstration of Core Mixin Operations

All fuzzy objects automatically inherit mixin functionalities, enabling direct and intuitive use.

import axisfuzzy as af
import numpy as np

md = np.array([0.8, 0.6, 0.9])
nmd = np.array([0.1, 0.3, 0.05])

fuzzy_array = af.fuzzyarray(np.array([md,nmd]), mtype='qrofn')

# Mixin operations work regardless of type
shape = fuzzy_array.shape          # Shape information
reshaped = fuzzy_array.reshape(3, 1)  # Reshape operation
flattened = fuzzy_array.flatten()  # Flatten to 1D
copied = fuzzy_array.copy()        # Deep copy

Key Functionality Groups

Shape and Dimensionality:

import axisfuzzy as af
import numpy as np

arr = af.fuzzyarray(np.array([[[0.8, 0.6], [0.7, 0.9]],
                            [[0.1, 0.3], [0.2, 0.1]]]), mtype='qrofn')

# Reshape array
reshaped = arr.reshape(4)  # or af.reshape(arr, 4)

# Flatten to 1D
flat = arr.flatten()

# Remove single dimensions
squeezed = arr.squeeze()

# Return flattened view
raveled = arr.ravel()

Data Transformation:

# Transpose array
transposed = arr.T  # or af.transpose(arr)

# Broadcast to new shape
broadcasted = arr.broadcast_to((3, 2, 2))

Container Manipulation:

arr1 = af.fuzzyarray(np.array([[0.8, 0.6], [0.1, 0.3]]), mtype='qrofn')
arr2 = af.fuzzyarray(np.array([[0.7, 0.9], [0.2, 0.1]]), mtype='qrofn')

# Concatenate arrays
combined = arr1.concat(arr2)  # or af.concat(arr1, arr2)

# Stack arrays along new axis
stacked = arr1.stack(arr2, axis=0)

# Append elements
extended = arr1.append(af.fuzzynum(md=0.5, nmd=0.4, mtype='qrofn'))

# Remove and return elements
item = arr1.pop(0)

Utilities and Inspection:

# Create deep copy
copied = af.copy(arr)

# Extract scalar item
scalar = arr.flatten().item(0,1)

# Boolean testing
has_any = arr.any()  # True if any element is "truthy"
all_true = arr.all()  # True if all elements are "truthy"

Advanced Structural Operations

Shape and Attribute Inspection

# Shape operations work on any fuzzy type
data = af.fuzzyarray(np.array([[[0.8, 0.6], [0.7, 0.9]], [[0.1, 0.3], [0.2, 0.1]]]), mtype='qrofn')

print(data.shape)           # (2, 2)
print(data.size)            # 4
print(data.ndim)            # 2

# Reshape operations
reshaped = data.reshape(4)  # Flatten to (4,)
expanded = data.reshape(2, 2, 1)  # Add dimension

Advanced Indexing and Slicing

# Advanced indexing works uniformly
data = af.fuzzyarray(np.array([[0.1, 0.5, 0.8, 0.3, 0.9], [0.8, 0.4, 0.1, 0.6, 0.05]]), mtype='qrohfn')

# Boolean indexing
high_values = data[data > 0.5]  # Elements > 0.5

# Fancy indexing
selected = data[[0, 2, 4]]      # Select specific indices

# Slice operations
subset = data[1:4]              # Slice notation

Type-Agnostic by Design

A core strength of the Mixin Operations is its type-agnostic nature, ensuring operational consistency across the entire fuzzy ecosystem.

# Same operations, different types
qrofn_data = af.random.rand('qrofn', shape=3)
qrohfn_data = af.random.rand('qrohfn', shape=3)

# All support the same mixin operations
for data in [qrofn_data, qrohfn_data]:
    print(f"Shape: {data.shape}")
    print(f"Max: {data.max()}")
    print(f"Mean: {data.mean()}")

Built-in Mixin Operations

The Mixin system provides a comprehensive set of pre-implemented operations that are available across all fuzzy number types. These operations are statically integrated into the core classes during framework initialization.

# Example of using built-in mixin operations
data = af.fuzzyarray(np.array([[0.8, 0.6, 0.9], [0.1, 0.3, 0.05]]), mtype='qrofn')

# Structural operations (available via mixin system)
reshaped = data.reshape(3, 1)  # Shape manipulation
transposed = data.T            # Transposition
flattened = data.flatten()     # Flattening

# Statistical operations
mean_val = data.mean()         # Mean calculation

# Utility operations
copied = data.copy()           # Deep copy

Seamless Integration with Core Classes

Mixin functions are automatically injected into the Fuzznum and Fuzzarray base classes, making them feel like native methods.

# All these work regardless of mtype
num = af.fuzzynum(md=0.8, nmd=0.2, mtype='qrofn')
arr = af.fuzzyarray(np.array([[0.7, 0.6], [0.2, 0.3]]), mtype='qrofn')

# Shape operations work on both
num_reshaped = num.reshape(1, 1)
arr_reshaped = arr.reshape(2, 1)

# # Copying works uniformly
num_copy = af.copy(num)
arr_copy = af.copy(arr)

External Extension Registration: Simplified API for External Users

Starting with AxisFuzzy v0.2.0, external extension registration has been simplified for external developers who define custom extensions after importing AxisFuzzy.

Quick Start Guide

Use the @external_extension decorator for automatic registration:

import axisfuzzy as af
from axisfuzzy.extension import external_extension

# Define external extension - automatically available!
@external_extension('custom_score', mtype='qrofn')
def my_score(self):
    """Custom scoring function."""
    return self.md ** 2 + self.nmd ** 2

# Use immediately without additional steps
fuzz = af.Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.3)
score = fuzz.custom_score()  # Works right away!

Extension Types and Examples

Instance Method (default):

@external_extension('complement', mtype='qrofn')
def qrofn_complement(self):
    return af.Fuzznum('qrofn', q=self.q).create(md=self.nmd, nmd=self.md)

comp = fuzz.complement()  # Called as method

Top-level Function:

@external_extension('similarity', mtype='qrofn',
                    injection_type='top_level_function')
def qrofn_similarity(x, y):
    return 1 - abs(x.md - y.md) - abs(x.nmd - y.nmd)

sim = af.similarity(fuzz1, fuzz2)  # Called as function

Instance Property:

@external_extension('uncertainty', mtype='qrofn',
                    injection_type='instance_property')
def qrofn_uncertainty(self):
    return 1 - (self.md**self.q + self.nmd**self.q)**(1/self.q)

value = fuzz.uncertainty  # Accessed as property (no parentheses)

Both Method and Function:

@external_extension('normalize', mtype='qrofn', injection_type='both')
def qrofn_normalize(x):
    total = x.md + x.nmd
    if total > 0:
        return af.Fuzznum('qrofn', q=x.q).create(md=x.md/total, nmd=x.nmd/total)
    return x

# Both work:
norm1 = fuzz.normalize()      # Method
norm2 = af.normalize(fuzz)    # Function

Advanced Options

Manual Application Control:

@external_extension('batch_op', mtype='qrofn', auto_apply=False)
def batch_operation(self):
    return "Deferred operation"

# Apply manually when ready
from axisfuzzy.extension import apply_extensions
apply_extensions(force_reapply=True)

Priority Settings:

# Higher priority overrides existing implementations
@external_extension('distance', mtype='qrofn', priority=10)
def improved_distance(x, y):
    return "Enhanced distance calculation"

Alternative Traditional Approach

For users who prefer explicit control, use the traditional approach:

from axisfuzzy.extension import extension, apply_extensions

@extension(name='traditional_method', mtype='qrofn')
def my_method(self):
    return self.md + self.nmd

# Manual application required
apply_extensions(force_reapply=True)

# Now available
result = fuzz.traditional_method()

Best Practices

  1. Use @external_extension for simplicity

  2. Choose descriptive names that indicate purpose and type

  3. Document your extensions with clear docstrings

  4. Test thoroughly with different fuzzy number configurations

  5. Handle edge cases like zero values or invalid inputs

Troubleshooting

If extensions don’t work:

  • Check mtype: Verify mtype matches your fuzzy object’s type

  • Verify import: Ensure you import from axisfuzzy.extension

  • Manual application: Try apply_extensions(force_reapply=True)

  • Check conflicts: Ensure no naming conflicts with existing methods

Warning

External extensions modify AxisFuzzy classes at runtime. Test thoroughly to avoid interference with existing functionality.

Tip

Use the priority parameter to handle conflicts when multiple extensions provide the same functionality.

Developer Guide: Creating Custom Extensions

This guide provides a comprehensive walkthrough for developers who wish to extend AxisFuzzy’s capabilities by creating custom, type-specific functionalities through the Extension System.

Note

The Extension System is the designated pathway for external contributions and domain-specific customizations. In contrast, the Mixin Operations is reserved for internal, universal structural operations.

When to Create an Extension

We recommend creating extensions for functionalities that are inherently tied to a specific fuzzy logic type. Key use cases include:

  1. Custom Fuzzy Operations: Novel arithmetic, aggregation, or logical operators.

  2. Domain-Specific Algorithms: Specialized algorithms for fields like decision-making, control systems, or image processing.

  3. Specialized Mathematical Functions: Unique distance metrics, similarity measures, or membership function evaluators.

  4. Type-Specific Behaviors: Any functionality that depends on the unique properties of a fuzzy type (e.g., the ‘q’ parameter in q-rung orthopair fuzzy sets).

Illustrative Examples

Custom Aggregation Functions:

# Custom aggregation for interval-valued fuzzy sets
@extension(name='weighted_avg', mtype='ivfs')
def interval_weighted_average(x, weights):
    """Weighted average for interval-valued fuzzy sets."""
    lower = np.average([iv.lower for iv in x], weights=weights)
    upper = np.average([iv.upper for iv in x], weights=weights)
    return IntervalValue(lower, upper)

Domain-Specific Distance Metrics:

# Hamming distance for type-II fuzzy sets
@extension(name='hamming_distance', mtype='t2fs')
def t2fs_hamming(x, y):
    """Hamming distance for type-II fuzzy sets."""
    return np.sum(np.abs(x.primary - y.primary) +
                 np.abs(x.secondary - y.secondary))

Specialized Membership Functions:

# Custom membership evaluation
@extension(name='evaluate_membership', mtype='qrofs')
def qrofs_membership(x, element):
    """Evaluate membership for Q-rung orthopair fuzzy sets."""
    md_dist = abs(x.md - element.md) ** x.q
    nmd_dist = abs(x.nmd - element.nmd) ** x.q
    return 1 - ((md_dist + nmd_dist) / 2) ** (1/x.q)

Best Practices for Extension Development

1. Adopt Descriptive Naming Conventions:

# GOOD: Descriptive and specific
@extension(name='euclidean_distance', mtype='qrofn')
def qrofn_euclidean_distance(x, y):
    pass

# BAD: Too generic
@extension(name='distance', mtype='qrofn')
def distance(x, y):
    pass

2. Write Comprehensive Docstrings:

@extension(name='custom_similarity', mtype='fs')
def fuzzy_similarity(x, y, method='cosine'):
    """
    Calculate similarity between fuzzy sets.

    Parameters
    ----------
    x, y : FuzzySet
        Input fuzzy sets
    method : str, default 'cosine'
        Similarity method ('cosine', 'jaccard', 'dice')

    Returns
    -------
    float
        Similarity value in [0, 1]
    """
    pass

3. Implement Robust Edge-Case Handling:

@extension(name='safe_division', mtype='qrofn')
def safe_qrofn_division(x, y, default=0.0):
    """Division with zero-handling for Q-rung numbers."""
    if y.md == 0 and y.nmd == 0:
        return default
    # Implement division logic
    pass

4. Prioritize Performance Optimization:

@extension(name='fast_aggregation', mtype='fs')
def optimized_aggregation(fuzzy_sets):
    """Optimized aggregation using vectorized operations."""
    # Use NumPy vectorization when possible
    values = np.array([fs.membership_values for fs in fuzzy_sets])
    return np.mean(values, axis=0)

Common Pitfalls to Avoid

1. Avoid Creating “Universal” Extensions: Extensions should be type-specific. If a function is universally applicable, it belongs in the Mixin Operations.

# BAD: This should be a mixin, not a multi-type extension
@extension(name='reshape', mtype='qrofn')
@extension(name='reshape', mtype='fs')
@extension(name='reshape', mtype='ivfs')
def reshape_for_each_type(self, *shape):
    # Same implementation for all types
    return self._data.reshape(*shape)

2. Keep Interfaces Clean and Simple: Avoid over-engineering simple operations with excessive parameters.

# BAD: Too many parameters for a simple operation
@extension(name='simple_add', mtype='fs')
def overcomplicated_addition(x, y, normalize=True,
                            method='algebraic',
                            confidence=0.95,
                            validation_level='strict'):
    # Keep extensions focused and simple
    pass

3. Enforce Type Safety: Always validate input types to prevent unexpected behavior.

# GOOD: Type checking
@extension(name='type_safe_operation', mtype='qrofn')
def safe_operation(x, y):
    if not isinstance(x, QRungOrthopairFuzzyNumber):
        raise TypeError(f"Expected QRungOrthopairFuzzyNumber, got {type(x)}")
    # Implementation
    pass

System Comparison and Architecture Understanding

Extension vs Mixin Comparison

This comparison helps understand the architectural differences between the two systems:

Characteristic

Extension System

Mixin Operations

Type Dependency

mtype-sensitive

mtype-agnostic

Mathematical Logic

Varies by type

Uniform across

Performance

Slight dispatch

Zero overhead

Use Cases

Distance, score, similarity

Reshape, concat, transpose

Extensibility

User-extensible (plugins)

Internal only (not extensible)

Understanding Extension Use Cases

Extensions are designed for:

  1. Type-specific Operations: The function behavior fundamentally depends on the fuzzy type

    # Q-rung specific similarity using q-parameter
    @extension(name='similarity', mtype='qrofn')
    def qrofn_similarity(x, y):
        # Q-rung specific similarity using q-parameter
        return ((x.md * y.md)**(1/x.q) + (x.nmd * y.nmd)**(1/x.q)) / 2
    
    @extension(name='similarity', mtype='fs')
    def fs_similarity(x, y):
        # Classical fuzzy similarity
        return min(x.membership, y.membership)
    
  2. Mathematical Operations: For operations rooted in fuzzy set theory

    # Fuzzy complement depends on the type's mathematical definition
    @extension(name='complement', mtype='qrofn')
    def qrofn_complement(self):
        # Q-rung orthopair complement
        return af.fuzzynum(self.nmd, self.md, mtype=self.mtype, q=self.q)
    
    @extension(name='complement', mtype='fs')
    def fs_complement(self):
        # Classical fuzzy complement
        return af.fuzzynum(1 - self.membership, mtype='fs')
    
  3. Domain Expertise Operations: Operations requiring deep understanding of fuzzy theory

    @extension(name='score_function', mtype='qrofn')
    def qrofn_score(self):
        """Calculate Q-rung orthopair score function."""
        return self.md**self.q - self.nmd**self.q
    
    @extension(name='hesitancy_degree', mtype='qrohfn')
    def qrohfn_hesitancy(self):
        """Calculate hesitancy degree for Q-rung orthopair hesitant fuzzy numbers."""
        return calculate_hesitancy_degree(self)
    

Mixins are designed for:

Note

Mixins are part of the core library’s internal architecture and are not user-extensible. The following examples illustrate the design principles behind built-in mixin operations.

  1. Universal Operations: When the operation works identically across all fuzzy types

    # Shape operations are universal (internal implementation)
    def reshape_array(self, *shape):
        """Reshape works the same for any fuzzy type."""
        return self._reshape_implementation(*shape)
    
    # Copying is universal (internal implementation)
    def copy_array(self):
        """Deep copy works the same for any fuzzy type."""
        return self._copy_implementation()
    
  2. Data Manipulation: For operations that manipulate the container structure

    # Indexing operations (internal implementation)
    def take_elements(self, indices):
        """Take elements at specified indices."""
        return self[indices]
    
    # Concatenation operations (internal implementation)
    def concatenate_arrays(arrays, axis=0):
        """Concatenate arrays along specified axis."""
        return generic_concatenate(arrays, axis)
    
  3. NumPy-like Interface: When you want familiar array programming patterns

    # Statistical operations that work on any numeric data (internal implementation)
    def calculate_mean(self, axis=None):
        """Calculate mean along specified axis."""
        return self._statistical_mean(axis)
    
    # Sorting operations (internal implementation)
    def sort_array(self, axis=-1):
        """Sort array along specified axis."""
        return self._sort_implementation(axis)
    

Understanding the Distinction

To help users understand when functionality belongs to Extensions vs Mixins, here are key architectural principles:

Type-Dependent vs Type-Agnostic Operations

# Type-dependent: Use Extensions
@extension(name='similarity', mtype='qrofn')
def qrofn_similarity(x, y):
    # Q-rung specific similarity calculation
    return ((x.md * y.md)**(1/x.q) + (x.nmd * y.nmd)**(1/x.q)) / 2

@extension(name='similarity', mtype='fs')
def fs_similarity(x, y):
    # Classical fuzzy similarity
    return min(x.membership, y.membership)

# Type-agnostic: Built into Mixins (not user-extensible)
# Example: arr.reshape(), arr.transpose(), arr.flatten()
# These work identically regardless of fuzzy type

Mathematical vs Structural Operations

# Mathematical operations: Use Extensions
@extension(name='complement', mtype='qrofn')
def qrofn_complement(self):
    # Mathematical complement depends on fuzzy type definition
    return af.fuzzynum(self.nmd, self.md, mtype=self.mtype, q=self.q)

# Structural operations: Built into Mixins
# Example: arr.copy(), arr.take(), arr.concatenate()
# These manipulate data structure, not mathematical content

Best Practices for Extension Development

When developing extensions for AxisFuzzy:

  1. Identify Type Dependency: Does your operation behavior fundamentally depend on the fuzzy type?

    • Yes → Develop as Extension

    • No → Consider if it should be a regular utility function

  2. Assess Mathematical Foundation: Is this operation rooted in fuzzy set theory?

    • Yes → Develop as Extension

    • No → Consider alternative approaches

  3. Evaluate Domain Specificity: Does the operation require deep understanding of specific fuzzy types?

    • Yes → Develop as Extension

    • No → Consider more general solutions

  4. Consider Reusability: Will this operation be useful across different projects?

    • Yes → Develop as Extension with proper documentation

    • No → Consider project-specific implementation

Performance Considerations

  • Extension System: First call has minimal dispatch overhead; subsequent calls are cached

  • Mixin Operations: Zero runtime overhead, equivalent to native method calls

  • Memory: Both systems have negligible memory impact

  • Scalability: Both scale well with large arrays and complex operations

Development Guidelines

For Extension Development:

  1. Always provide clear mtype specifications

  2. Include default implementations when appropriate

  3. Use descriptive function names that indicate purpose

  4. Document mathematical formulations in docstrings

  5. Test with multiple fuzzy number types

For Mixin Development:

  1. Ensure operations work uniformly across all mtypes

  2. Follow NumPy conventions for parameter names and behavior

  3. Delegate to factory functions for actual implementation

  4. Maintain consistency with existing array operations

  5. Consider both Fuzznum and Fuzzarray use cases

Available Mixin Operations

Shape and Structure:

  • shape: Array dimensions

  • size: Total number of elements

  • ndim: Number of dimensions

  • reshape(): Change array shape

  • flatten(): Flatten to 1D

  • squeeze(): Remove single-dimensional entries

  • expand_dims(): Add new dimensions

Indexing and Access:

  • item(): Extract single element

  • take(): Take elements along axis

  • compress(): Select elements using condition

  • choose(): Choose elements from multiple arrays

Data Manipulation:

  • copy(): Deep copy

  • view(): Memory view

  • astype(): Type conversion

  • fill(): Fill with value

  • repeat(): Repeat elements

  • tile(): Tile array

Aggregation:

  • min(), max(): Minimum/maximum values

  • mean(), std(): Statistical measures

  • sum(), prod(): Reduction operations

  • any(), all(): Boolean aggregation

Sorting and Searching:

  • sort(): Sort array

  • argsort(): Sort indices

  • searchsorted(): Binary search

  • partition(): Partial sort

Performance Considerations

Extensions:

  • Type-specific optimizations possible

  • Direct access to type internals

  • Can leverage specialized algorithms

  • Minimal dispatch overhead

Mixins:

  • Generic implementations

  • May require type adaptation

  • Optimized for common patterns

  • Slightly higher dispatch overhead

Recommendation: Choose based on correctness first, then optimize if needed.

Summary and Best Practices

Key Takeaways for Extension Development

  1. Extensions are the primary way for external users to extend AxisFuzzy

  2. Focus on type-specific mathematical and fuzzy logic operations

  3. Keep extensions simple and focused on a single responsibility

  4. Document thoroughly with clear examples and parameter descriptions

  5. Test comprehensively including edge cases and performance scenarios

  6. Follow naming conventions that clearly describe the operation

Development Workflow for Extensions

  1. Identify the fuzzy operation you want to implement

  2. Determine the target fuzzy type(s) (mtype parameter)

  3. Design the function signature with clear parameters

  4. Implement with proper error handling and type checking

  5. Write comprehensive tests for various scenarios

  6. Document the extension with examples and use cases

  7. Consider performance optimization if needed

Extension System Benefits

  • Type Safety: Automatic dispatch to correct implementation

  • Performance: Direct access to type-specific optimizations

  • Flexibility: Easy to add new operations without modifying core

  • Maintainability: Clear separation between core and custom functionality

  • Extensibility: Support for future fuzzy types and operations

Conclusion

The Extension System provides a powerful and flexible way to extend AxisFuzzy with custom fuzzy logic operations. By following the guidelines in this document, you can create robust, efficient, and maintainable extensions that integrate seamlessly with AxisFuzzy’s architecture.

The dual-track architecture ensures that:

  • External users can easily add custom functionality through extensions

  • Core developers can maintain universal operations through mixins

  • Both systems work together to provide a comprehensive fuzzy computing platform

For most users, the pre-built extensions will be sufficient for common fuzzy operations. Advanced users and researchers can create custom extensions for specialized domain-specific operations, mathematical functions, and novel fuzzy logic algorithms.

By leveraging the Extension System effectively, you can build powerful, maintainable fuzzy computing applications tailored to your specific needs.

See Also