Extension Methods Development Guide

This development guide provides comprehensive instructions for implementing custom extension methods for fuzzy number types in AxisFuzzy. The guide demonstrates the complete development process using Q-Rung Orthopair Fuzzy Numbers (QROFN) as a reference example, covering architecture design, implementation patterns, registration workflows, and integration procedures.

The extension system enables developers to create type-specific methods that seamlessly integrate with AxisFuzzy’s core functionality, providing polymorphic behavior based on the fuzzy number’s mathematical type (mtype). This guide focuses on the practical aspects of developing, registering, and deploying extension methods for custom fuzzy number types.

Note

Enhanced External Extension Support: Starting with AxisFuzzy v0.2.0, external extension registration has been streamlined with new APIs that automatically handle extension injection. See External Extension Development: Quick Guide for Third-Party Developers for comprehensive guidance on external extension development.

Extension System Architecture Overview

AxisFuzzy’s extension system implements a sophisticated Register-Dispatch-Inject architecture pattern that enables seamless integration of type-specific functionality for fuzzy number types. This system provides a clean separation between extension registration, runtime dispatch, and method injection, ensuring both flexibility and performance.

Design Principles and Architectural Pillars

The extension system is built upon three fundamental design principles:

  1. Type-based Polymorphism: Extensions are dispatched based on the mtype attribute of fuzzy objects, enabling specialized implementations for different fuzzy number types (e.g., qrofn, ifn, pfn).

  2. Declarative Registration: The @extension decorator provides a clean, declarative API for registering extension functions without requiring manual registry manipulation.

  3. Flexible Injection: Extensions can be exposed as instance methods, instance properties, top-level functions, or both, depending on the intended usage pattern.

Architectural Flow:

Registration Phase:    @extension decorator → ExtensionRegistry
Initialization Phase:  ExtensionRegistry → ExtensionDispatcher → ExtensionInjector
Runtime Phase:         Method call → Dispatcher → Type-specific implementation

Core Components Overview

ExtensionRegistry

The ExtensionRegistry serves as the central repository for all registered extension functions. It maintains a thread-safe mapping between function names, fuzzy types (mtype), and their corresponding implementations.

Key Features:

  • Thread-safe registration and lookup operations

  • Support for both specialized (type-specific) and default implementations

  • Priority-based resolution for handling multiple implementations

  • Comprehensive metadata storage including target classes and injection types

Registration Example:

from axisfuzzy.extension import extension

@extension(name='distance', mtype='qrofn', target_classes=['Fuzznum'])
def qrofn_distance(x, y, p=2):
    # QROFN-specific distance implementation
    return ((abs(x.md**x.q - y.md**x.q)**p + abs(x.nmd**x.q - y.nmd**x.q)**p) / 2)**(1/p)

ExtensionDispatcher

The ExtensionDispatcher creates dynamic proxy callables that resolve the correct implementation at runtime based on the mtype of involved fuzzy objects.

Proxy Types:

  • Instance Method Proxies: Callable as obj.method(...) with automatic mtype resolution from obj

  • Instance Property Proxies: Accessed as obj.property for read-only computed attributes

  • Top-level Function Proxies: Callable as axisfuzzy.function(obj, ...) with mtype resolution from arguments

Dispatch Resolution Logic:

# Runtime dispatch example
def dispatched_distance(self, other, **kwargs):
    mtype = self.mtype  # Extract from instance
    impl = registry.get_function('distance', mtype)  # Lookup implementation
    if impl is None:
        impl = registry.get_function('distance', None)  # Fallback to default
    return impl(self, other, **kwargs)  # Invoke resolved implementation

ExtensionInjector

The ExtensionInjector orchestrates the final step of making extensions available to users by attaching dispatcher-created proxies to target classes and the module namespace.

Injection Process:

  1. Scan registry metadata to determine target classes and injection types

  2. Create appropriate dispatcher proxies for each extension

  3. Attach proxies to Fuzznum, Fuzzarray classes or axisfuzzy module namespace

  4. Avoid overwriting existing attributes to prevent conflicts

The @extension Decorator API

The @extension decorator provides the primary interface for registering extension functions. It accepts several parameters that control how the extension is registered and exposed:

Core Parameters:

@extension(
    name='function_name',           # Extension name (required)
    mtype='qrofn',                 # Target fuzzy type (None for default)
    target_classes=['Fuzznum'],    # Target classes for injection
    injection_type='both',         # How to expose: 'instance_method', 'instance_property',
                                  # 'top_level_function', or 'both'
    is_default=False,              # Whether this is a fallback implementation
    priority=0                     # Resolution priority (higher wins)
)

Usage Patterns:

# Instance method for specific type
@extension('distance', mtype='qrofn', target_classes=['Fuzznum'])
def qrofn_distance(x, y): ...

# Instance property
@extension('score', mtype='qrofn', injection_type='instance_property')
def qrofn_score(obj): ...

# Top-level function only
@extension('read_csv', mtype='qrofn', injection_type='top_level_function')
def qrofn_read_csv(filename): ...

# Default fallback implementation
@extension('normalize', is_default=True)
def default_normalize(x): ...

Type-based Polymorphic Dispatch

The extension system achieves polymorphism through runtime mtype resolution. When an extension method is called, the dispatcher examines the mtype attribute of the primary fuzzy object to select the appropriate implementation.

Dispatch Priority:

  1. Exact Match: Look for implementation registered with specific mtype

  2. Default Fallback: Use implementation registered with mtype=None if available

  3. Error: Raise informative error listing available types and suggesting alternatives

Example Dispatch Flow:

# User calls: my_qrofn.distance(other_qrofn)
# 1. Dispatcher extracts mtype='qrofn' from my_qrofn
# 2. Registry lookup: get_function('distance', 'qrofn')
# 3. Found qrofn_distance implementation
# 4. Invoke: qrofn_distance(my_qrofn, other_qrofn)

Integration with Core Data Structures

The extension system integrates seamlessly with AxisFuzzy’s core Fuzznum and Fuzzarray classes through the injection mechanism. Extensions become first-class methods and properties of these classes, providing a natural and intuitive user experience.

Integration Points:

  • Fuzznum Class: Single fuzzy number operations (distance, comparison, properties)

  • Fuzzarray Class: Array-based operations (aggregation, I/O, broadcasting)

  • Module Namespace: Factory functions and utilities (constructors, file I/O)

This architecture ensures that custom fuzzy types can leverage the full power of AxisFuzzy’s extension ecosystem while maintaining clean separation of concerns and optimal runtime performance.

Extension Method Implementation Development

This section provides a comprehensive guide for implementing extension methods using QROFN (q-rung Orthopair Fuzzy Numbers) as a practical example. The implementation follows a structured approach that ensures consistency, maintainability, and optimal performance.

Extension Method Categories

AxisFuzzy extension methods are organized into five primary categories, each serving distinct computational purposes:

Constructor Methods

Create new fuzzy objects with specific initialization patterns. These methods provide convenient factory functions for common object creation scenarios.

I/O Operations

Handle data serialization and deserialization across multiple formats (CSV, JSON, NumPy binary). These methods enable seamless data exchange and persistence.

Mathematical Operations

Implement aggregation functions and statistical computations using fuzzy-specific algorithms that respect the mathematical properties of each fuzzy type.

Measurement Functions

Calculate distances, similarities, and other metrics between fuzzy objects using type-appropriate formulas.

Property Accessors

Provide computed properties that extract meaningful characteristics from fuzzy objects, such as scores and indeterminacy measures.

Implementation Structure: ext/ Directory Organization

The QROFN implementation demonstrates the recommended modular organization pattern:

axisfuzzy/fuzztype/qrofs/
├── ext/
│   ├── constructor.py    # Factory methods for object creation
│   ├── io.py            # Serialization and data exchange
│   ├── ops.py           # Mathematical and aggregation operations
│   ├── measure.py       # Distance and similarity calculations
│   └── string.py        # String parsing and conversion utilities
└── extension.py         # Extension registration and decorator usage

This modular structure promotes code reusability, simplifies maintenance, and provides clear separation of concerns.

Core Extension Method Types with QROFN Examples

The following table summarizes all required extension methods for a complete fuzzy type implementation:

QROFN Extension Methods Reference

Method Name

Category

Injection Type

Purpose

empty

Constructor

Top-level Function

Create uninitialized QROFN objects

positive

Constructor

Top-level Function

Create objects with maximum membership (md=1, nmd=0)

negative

Constructor

Top-level Function

Create objects with maximum non-membership (md=0, nmd=1)

full

Constructor

Top-level Function

Create objects filled with specific values

empty_like

Constructor

Top-level Function

Create uninitialized objects matching input shape

positive_like

Constructor

Top-level Function

Create positive objects matching input shape

negative_like

Constructor

Top-level Function

Create negative objects matching input shape

full_like

Constructor

Top-level Function

Create filled objects matching input shape

to_csv

I/O Operations

Instance Method

Export fuzzy arrays to CSV format

read_csv

I/O Operations

Top-level Function

Import fuzzy arrays from CSV files

to_json

I/O Operations

Instance Method

Export fuzzy arrays to JSON format

read_json

I/O Operations

Top-level Function

Import fuzzy arrays from JSON files

to_npy

I/O Operations

Instance Method

Export fuzzy arrays to NumPy binary format

read_npy

I/O Operations

Top-level Function

Import fuzzy arrays from NumPy binary files

sum

Mathematical

Instance Method

Aggregate using t-conorm reduction

mean

Mathematical

Instance Method

Calculate fuzzy arithmetic mean

max

Mathematical

Instance Method

Find maximum based on score function

min

Mathematical

Instance Method

Find minimum based on score function

prod

Mathematical

Instance Method

Aggregate using t-norm reduction

var

Mathematical

Instance Method

Calculate fuzzy variance

std

Mathematical

Instance Method

Calculate fuzzy standard deviation

distance

Measurement

Instance Method

Compute distance between fuzzy objects

score

Property

Instance Property

Calculate membership score (md^q - nmd^q)

acc

Property

Instance Property

Calculate accuracy measure

ind

Property

Instance Property

Calculate indeterminacy degree

str2fuzznum

String Conversion

Top-level Function

Parse string representation to Fuzznum

Implementation Patterns and Best Practices

Performance Optimization

Leverage backend component arrays directly for vectorized operations. The QROFN implementation demonstrates this pattern in I/O operations:

def _qrofn_to_csv(arr: Fuzzarray, path: str, **kwargs) -> None:
    # Get component arrays directly from backend for efficiency
    mds, nmds = arr.backend.get_component_arrays()

    # Use vectorized string operations
    str_data = np.char.add(
        np.char.add('<', mds.astype(str)),
        np.char.add(',', np.char.add(nmds.astype(str), '>'))
    )
Type-Specific Algorithm Integration

Mathematical operations should utilize the appropriate t-norm/t-conorm operations for the fuzzy type:

def _qrofn_sum(arr: Union[Fuzznum, Fuzzarray], axis=None):
    op_registry = get_registry_operation()
    norm_type, params = op_registry.get_default_t_norm_config()
    tnorm = OperationTNorm(norm_type=norm_type, q=arr.q, **params)

    mds, nmds = arr.backend.get_component_arrays()
    md_sum = tnorm.t_conorm_reduce(mds, axis=axis)
    nmd_sum = tnorm.t_norm_reduce(nmds, axis=axis)
Error Handling and Validation

Implement comprehensive input validation and provide meaningful error messages:

def _qrofn_distance(fuzz_1, fuzz_2, p_l=2, indeterminacy=True):
    if fuzz_1.q != fuzz_2.q:
        raise ValueError(f"Q-rung mismatch: {fuzz_1.q} != {fuzz_2.q}")
    if fuzz_1.mtype != fuzz_2.mtype:
        raise ValueError(f"Type mismatch: {fuzz_1.mtype} != {fuzz_2.mtype}")

Type-Specific Algorithm Development Guidelines

When developing algorithms for custom fuzzy types, consider these essential principles:

  1. Mathematical Consistency: Ensure all operations respect the mathematical constraints of your fuzzy type

  2. Backend Integration: Utilize the backend’s component array access for optimal performance

  3. Axis-Aware Operations: Support axis-specific reductions for multi-dimensional arrays

  4. Fallback Handling: Provide graceful degradation for edge cases (empty arrays, single elements)

  5. Parameter Validation: Validate type-specific parameters (e.g., q-rung values for QROFN)

Extension Registration and Integration Workflow

This section outlines the complete workflow for registering and integrating extension methods into the AxisFuzzy framework. The registration process transforms individual implementation functions into dynamically accessible methods and properties on core fuzzy objects.

Extension Method Registration Using @extension Decorator

The @extension decorator serves as the primary interface for registering extension functions. Each extension function must be wrapped with this decorator to become part of the AxisFuzzy extension system.

Basic Registration Pattern

from axisfuzzy.extension import extension
from .ext import _qrofn_sum  # Import the implementation function

@extension(
    name='sum',
    mtype='qrofn',
    target_classes=['Fuzzarray', 'Fuzznum']
)
def qrofn_sum_ext(fuzz, axis=None):
    """Aggregate QROFN values using t-conorm reduction."""
    return _qrofn_sum(fuzz, axis=axis)

Decorator Parameter Configuration

The @extension decorator accepts several parameters that control registration behavior:

@extension Decorator Parameters

Parameter

Type

Description

name

str (required)

The method/function name as it will appear to users

mtype

str or None

Target fuzzy type (‘qrofn’, ‘ivfs’, etc.). None for default implementations

target_classes

List[str]

Classes to inject into: [‘Fuzznum’], [‘Fuzzarray’], or [‘Fuzznum’, ‘Fuzzarray’]

injection_type

str

How to expose: ‘instance_method’, ‘instance_property’, ‘top_level_function’, or ‘both’

is_default

bool

Whether this serves as a fallback implementation for unspecified mtypes

priority

int

Resolution priority when multiple implementations exist (higher wins)

Parameter Configuration for Different Injection Types

Instance Method Injection

Creates methods callable on Fuzznum/Fuzzarray instances. This is the most common injection type for operations that act on existing fuzzy objects:

@extension(name='distance', mtype='qrofn', injection_type='instance_method')
def qrofn_distance_ext(self, other, p_l=2, indeterminacy=True):
    return _qrofn_distance(self, other, p_l, indeterminacy)

# Usage: fuzz_obj.distance(other_obj, p_l=3)
Instance Property Injection

Creates read-only properties accessible via attribute access. Ideal for computed characteristics:

@extension(name='score', mtype='qrofn', injection_type='instance_property')
def qrofn_score_ext(self):
    return _qrofn_score(self)

# Usage: fuzz_obj.score
Top-level Function Injection

Creates functions in the axisfuzzy module namespace. Used for constructor functions and static operations:

@extension(name='empty', mtype='qrofn', injection_type='top_level_function')
def qrofn_empty_ext(shape=None, q=None):
    return _qrofn_empty(shape, q)

# Usage: axisfuzzy.empty(shape=(3, 3), q=2)
Both Injection Type

Combines instance method and top-level function injection for maximum accessibility:

@extension(name='sum', mtype='qrofn', injection_type='both')
def qrofn_sum_ext(fuzz, axis=None):
    return _qrofn_sum(fuzz, axis)

# Usage: fuzz_obj.sum(axis=0) or axisfuzzy.sum(fuzz_obj, axis=0)

Integration with Target Classes

The extension system integrates with AxisFuzzy’s core classes through dynamic method injection. The integration process respects the existing class hierarchy and avoids conflicts with built-in methods.

Target Class Specification

Use target_classes to control which classes receive the extension:

# Only inject into Fuzzarray (for array-specific operations)
@extension(name='to_csv', mtype='qrofn', target_classes=['Fuzzarray'])

# Inject into both classes (for universal operations)
@extension(name='sum', mtype='qrofn', target_classes=['Fuzznum', 'Fuzzarray'])
Method Resolution and Dispatch

At runtime, the extension system automatically resolves the correct implementation based on the object’s mtype:

qrofn_obj = Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.3)
ivfs_obj = Fuzznum('ivfs').create(lower=0.6, upper=0.9)

# Automatically dispatches to QROFN-specific implementation
qrofn_score = qrofn_obj.score

# Automatically dispatches to IVFS-specific implementation
ivfs_score = ivfs_obj.score

The apply_extensions() Function and Its Critical Role

The apply_extensions() function serves as the master activation switch for the entire extension system. This function must be called to make registered extensions available to users.

Function Signature and Purpose

def apply_extensions() -> bool:
    """
    Applies all registered extension functions to their respective targets.

    Returns:
        bool: True if extensions were applied successfully, False otherwise.
    """
Integration Process

The function performs these critical steps:

  1. Dynamic Class Discovery: Locates Fuzznum and Fuzzarray classes at runtime to avoid circular imports

  2. Module Namespace Resolution: Identifies the axisfuzzy module for top-level function injection

  3. Extension Injection: Delegates to ExtensionInjector to attach all registered extensions

  4. Idempotency Guarantee: Ensures safe multiple calls without duplicate injection

Typical Usage Pattern

The function is automatically called during AxisFuzzy initialization, but can be invoked manually when needed:

# Automatic call during import (typical case)
import axisfuzzy  # apply_extensions() called internally

# Manual call after registering new extensions
from axisfuzzy.extension import apply_extensions

# Register your custom extensions here...

# Activate the extensions
success = apply_extensions()
if not success:
    print("Warning: Extension application failed")

Testing and Validation of Registered Extensions

Comprehensive testing ensures that registered extensions function correctly and integrate seamlessly with the framework.

Basic Functionality Testing

def test_qrofn_sum_extension():
    """Test QROFN sum extension registration and functionality."""
    # Create test data
    arr = axisfuzzy.empty((2, 2), mtype='qrofn', q=2)
    arr[0, 0] = Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.2)
    arr[0, 1] = Fuzznum('qrofn', q=2).create(md=0.6, nmd=0.3)

    # Test instance method access
    result = arr.sum(axis=0)
    assert isinstance(result, Fuzzarray)
    assert result.mtype == 'qrofn'

    # Test top-level function access
    result2 = axisfuzzy.sum(arr, axis=0)
    assert np.allclose(result.md, result2.md)

Integration Testing

def test_extension_injection_completeness():
    """Verify all required extensions are properly injected."""
    required_methods = ['sum', 'mean', 'max', 'min', 'distance']
    required_properties = ['score', 'acc', 'ind']
    required_functions = ['empty', 'positive', 'negative', 'read_csv']

    # Test instance methods
    fuzz_obj = Fuzznum('qrofn', q=2).create(md=0.7, nmd=0.2)
    for method in required_methods:
        assert hasattr(fuzz_obj, method), f"Missing method: {method}"

    # Test instance properties
    for prop in required_properties:
        assert hasattr(fuzz_obj, prop), f"Missing property: {prop}"

    # Test top-level functions
    import axisfuzzy
    for func in required_functions:
        assert hasattr(axisfuzzy, func), f"Missing function: {func}"

Deployment Considerations and Best Practices

Successfully deploying extension methods requires careful attention to several critical aspects that can significantly impact both development efficiency and runtime performance. This section provides practical guidance based on real-world experience developing the QROFN extension system.

Managing Extension Loading and Initialization

The timing of extension registration is crucial for proper system initialization. Extensions must be registered before the apply_extensions() function is called, which typically occurs during AxisFuzzy’s import process. The QROFN implementation demonstrates the recommended approach:

# In axisfuzzy/fuzztype/qrofs/extension.py
from axisfuzzy.extension import extension
from .ext import (
    _qrofn_sum, _qrofn_mean, _qrofn_distance,
    _qrofn_empty, _qrofn_to_csv, _qrofn_from_str
)

# All extensions are registered at module import time
@extension(name='sum', mtype='qrofn', target_classes=['Fuzzarray', 'Fuzznum'])
def qrofn_sum_ext(fuzz, axis=None):
    return _qrofn_sum(fuzz, axis=axis)

# Additional registrations follow...

This pattern ensures that when users import AxisFuzzy, all QROFN extensions are immediately available. The key insight is that extension registration happens at import time, but the actual method injection occurs later when apply_extensions() is called.

Debugging Extension Loading Issues

When extensions don’t appear to be working, the most common cause is import order problems. You can verify extension registration status by checking the registry directly:

from axisfuzzy.extension.registry import get_extension_registry

registry = get_registry_extension()
print(f"Registered functions: {list(registry.list_functions())}")
print(f"QROFN sum available: {registry.get_metadata('sum', 'qrofn')}")

Optimizing Performance in Extension Functions

Extension functions often become performance bottlenecks because they’re called frequently during computations. The QROFN implementation incorporates several optimization strategies that significantly improve runtime performance.

Leveraging Backend Component Arrays

Direct access to backend component arrays eliminates unnecessary object creation and enables vectorized operations. The actual implementation in ops.py demonstrates this pattern:

def _qrofn_sum(arr: Union[Fuzznum, Fuzzarray], axis=None):
    # Efficient: Direct backend access for component arrays
    mds, nmds = arr.backend.get_component_arrays()

    # Use t-norm/t-conorm operations for proper fuzzy aggregation
    op_registry = get_registry_operation()
    norm_type, params = op_registry.get_default_t_norm_config()
    tnorm = OperationTNorm(norm_type=norm_type, q=arr.q, **params)

    md_sum = tnorm.t_conorm_reduce(mds, axis=axis)
    nmd_sum = tnorm.t_norm_reduce(nmds, axis=axis)

    # Return appropriate type based on axis parameter
    if axis is None:
        return Fuzznum('qrofn', q=arr.q).create(md=md_sum, nmd=nmd_sum)
    else:
        backend_cls = arr.backend.__class__
        new_backend = backend_cls.from_arrays(md_sum, nmd_sum, q=arr.q)
        return Fuzzarray(backend=new_backend)

Efficient String-Based I/O Operations

The CSV I/O implementation in io.py uses efficient string operations without external dependencies:

def _qrofn_to_csv(arr: Fuzzarray, path: str, **kwargs) -> None:
    """High-performance CSV export using backend arrays directly."""
    # Get component arrays directly from backend
    mds, nmds = arr.backend.get_component_arrays()

    # Create string representation efficiently using numpy char operations
    str_data = np.char.add(
        np.char.add('<', mds.astype(str)),
        np.char.add(',', np.char.add(nmds.astype(str), '>'))
    )

    # Write directly to CSV without pandas dependency
    with open(path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f, **kwargs)
        if str_data.ndim == 1:
            writer.writerow(str_data)
        else:
            writer.writerows(str_data)

Vectorized Distance Computations

The distance calculation in measure.py demonstrates efficient vectorized operations for different input combinations:

def _qrofn_distance(fuzz_1, fuzz_2, p_l=2, indeterminacy=True):
    """High-performance distance calculation with vectorized operations."""
    # Handle Fuzzarray vs Fuzzarray case with full vectorization
    if isinstance(fuzz_1, Fuzzarray) and isinstance(fuzz_2, Fuzzarray):
        mds1, nmds1 = fuzz_1.backend.get_component_arrays()
        mds2, nmds2 = fuzz_2.backend.get_component_arrays()

        # Vectorized indeterminacy calculation
        pi1 = (1 - mds1 ** q - nmds1 ** q) ** (1 / q)
        pi2 = (1 - mds2 ** q - nmds2 ** q) ** (1 / q)
        pi = np.abs(pi1 ** q - pi2 ** q) ** p_l

        # Vectorized distance computation
        if indeterminacy:
            distance = (0.5 * (np.abs(mds1 ** q - mds2 ** q) ** p_l +
                               np.abs(nmds1 ** q - nmds2 ** q) ** p_l + pi)) ** (1 / p_l)
        return distance

Robust Error Handling and User Guidance

Extension functions should provide clear, actionable error messages that help users understand and resolve issues quickly. The QROFN implementation demonstrates several effective error handling patterns.

Parameter Validation with Contextual Messages

def _qrofn_distance(fuzz_1, fuzz_2, p_l=2, indeterminacy=True):
    # Validate q-rung compatibility
    if fuzz_1.q != fuzz_2.q:
        raise ValueError(
            f"Cannot compute distance between QROFN objects with different q-rungs: "
            f"{fuzz_1.q} and {fuzz_2.q}. Consider converting to the same q-rung first."
        )

    # Validate distance parameter
    if p_l <= 0:
        raise ValueError(
            f"Distance parameter p_l must be positive, got {p_l}. "
            f"Common values are 1 (Manhattan), 2 (Euclidean), or inf (Chebyshev)."
        )

Graceful Handling of Edge Cases

def _qrofn_mean(arr: Union[Fuzznum, Fuzzarray], axis=None):
    if arr.size == 0:
        raise ValueError(
            "Cannot compute mean of empty array. "
            "Use axisfuzzy.empty() to create arrays with default values."
        )

    if arr.size == 1:
        # Single element case - return copy to maintain consistency
        return arr.copy()

    # Normal computation for multiple elements
    mds, nmds = arr.backend.get_component_arrays()
    # ... rest of implementation

This comprehensive approach to deployment ensures that your extension methods integrate seamlessly with AxisFuzzy while providing a robust, performant, and maintainable foundation for users.

External Extension Development: Quick Guide for Third-Party Developers

This guide shows how to create and deploy external extensions for AxisFuzzy libraries and applications. External extensions allow you to add custom functionality without modifying the core AxisFuzzy codebase.

Quick Start: Creating External Extensions

Step 1: Choose the Right Decorator

For external projects, use @external_extension (recommended):

# my_fuzzy_extensions.py
import axisfuzzy as af
from axisfuzzy.extension import external_extension

@external_extension('custom_distance', mtype='qrofn')
def my_distance(self, other):
    """Custom distance function."""
    return abs(self.md - other.md) + abs(self.nmd - other.nmd)

# Automatically available - no setup needed!
fuzz1 = af.Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.3)
fuzz2 = af.Fuzznum('qrofn', q=2).create(md=0.6, nmd=0.4)
dist = fuzz1.custom_distance(fuzz2)

Step 2: Choose Injection Type

# Instance method (default)
@external_extension('my_method', mtype='qrofn')
def method_func(self):
    return self.md + self.nmd

# Usage: fuzz.my_method()

# Top-level function
@external_extension('my_function', mtype='qrofn',
                    injection_type='top_level_function')
def function_func(x, y):
    return x.md * y.md

# Usage: af.my_function(fuzz1, fuzz2)

# Instance property
@external_extension('my_property', mtype='qrofn',
                    injection_type='instance_property')
def property_func(self):
    return self.md ** 2

# Usage: fuzz.my_property (no parentheses)

# Both method and function
@external_extension('my_both', mtype='qrofn', injection_type='both')
def both_func(x):
    return x.md * 2

# Usage: fuzz.my_both() OR af.my_both(fuzz)

Common Use Cases

Custom Similarity Measures:

@external_extension('cosine_similarity', mtype='qrofn')
def cosine_sim(self, other):
    """Cosine similarity for QROFN."""
    numerator = self.md * other.md + self.nmd * other.nmd
    denom = ((self.md**2 + self.nmd**2) * (other.md**2 + other.nmd**2))**0.5
    return numerator / denom if denom > 0 else 0

Custom Aggregation Functions:

@external_extension('weighted_mean', mtype='qrofn',
                    injection_type='top_level_function')
def weighted_aggregation(fuzzy_list, weights):
    """Weighted mean aggregation."""
    total_weight = sum(weights)
    md_sum = sum(f.md * w for f, w in zip(fuzzy_list, weights))
    nmd_sum = sum(f.nmd * w for f, w in zip(fuzzy_list, weights))
    return af.Fuzznum('qrofn', q=fuzzy_list[0].q).create(
        md=md_sum/total_weight, nmd=nmd_sum/total_weight)

Custom Properties:

@external_extension('entropy', mtype='qrofn',
                    injection_type='instance_property')
def qrofn_entropy(self):
    """Calculate entropy measure."""
    import math
    if self.md > 0 and self.nmd > 0:
        return -(self.md * math.log(self.md) + self.nmd * math.log(self.nmd))
    return 0

Library Packaging

Package Structure:

my_fuzzy_lib/
├── __init__.py          # Main package
├── extensions.py        # Extension definitions
├── utils.py            # Helper functions
└── tests/              # Test suite
    └── test_extensions.py

Main Package (``__init__.py``):

# my_fuzzy_lib/__init__.py
"""My Fuzzy Extensions Library."""

__version__ = "1.0.0"

# Import extensions to register them
from . import extensions

# Verify AxisFuzzy is available
try:
    import axisfuzzy as af
except ImportError:
    raise ImportError("my_fuzzy_lib requires axisfuzzy")

# Optional: verify extensions loaded
def verify_extensions():
    """Check if extensions are available."""
    from axisfuzzy.extension import apply_extensions
    return apply_extensions(force_reapply=True)

Extensions Module (``extensions.py``):

# my_fuzzy_lib/extensions.py
"""Extension definitions."""

import axisfuzzy as af
from axisfuzzy.extension import external_extension

@external_extension('lib_distance', mtype='qrofn')
def custom_distance(self, other, method='euclidean'):
    """Custom distance with multiple methods."""
    if method == 'euclidean':
        return ((self.md - other.md)**2 + (self.nmd - other.nmd)**2)**0.5
    elif method == 'manhattan':
        return abs(self.md - other.md) + abs(self.nmd - other.nmd)
    else:
        raise ValueError(f"Unknown method: {method}")

@external_extension('lib_score', mtype='qrofn',
                    injection_type='instance_property')
def custom_score(self):
    """Custom scoring function."""
    return self.md**self.q - self.nmd**self.q

Advanced Options

Priority Control:

# Higher priority overrides existing implementations
@external_extension('distance', mtype='qrofn', priority=10)
def improved_distance(self, other):
    return "Better distance algorithm"

Manual Application:

# Defer automatic application
@external_extension('batch_method', mtype='qrofn', auto_apply=False)
def batch_operation(self):
    return "Batch processing"

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

Conditional Extensions:

# Only register if dependencies available
try:
    import numpy as np

    @external_extension('numpy_op', mtype='qrofn')
    def numpy_operation(self):
        return np.array([self.md, self.nmd])
except ImportError:
    pass  # Skip if NumPy not available

Best Practices

  1. Use descriptive names: Choose names that clearly indicate functionality

  2. Add docstrings: Document parameters, returns, and examples

  3. Handle errors: Check inputs and provide meaningful error messages

  4. Test thoroughly: Test with different fuzzy types and edge cases

  5. Version compatibility: Specify minimum AxisFuzzy version requirements

Testing Your Extensions

# test_extensions.py
import pytest
import axisfuzzy as af

def test_custom_distance():
    """Test custom distance function."""
    fuzz1 = af.Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.3)
    fuzz2 = af.Fuzznum('qrofn', q=2).create(md=0.6, nmd=0.4)

    # Test method exists
    assert hasattr(fuzz1, 'custom_distance')

    # Test functionality
    dist = fuzz1.custom_distance(fuzz2)
    assert isinstance(dist, float)
    assert dist >= 0

def test_custom_score():
    """Test custom score property."""
    fuzz = af.Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.3)

    # Test property exists
    assert hasattr(fuzz, 'custom_score')

    # Test value
    score = fuzz.custom_score
    assert isinstance(score, float)

Deployment

Installation Order:

pip install axisfuzzy>=0.2.0
pip install my-fuzzy-lib

Usage:

import axisfuzzy as af
import my_fuzzy_lib  # Extensions auto-register

# Use extensions immediately
fuzz = af.Fuzznum('qrofn', q=2).create(md=0.8, nmd=0.3)
result = fuzz.custom_distance(other_fuzz)

Conclusion

This development guide demonstrates the complete workflow for implementing custom fuzzy type extensions in AxisFuzzy. Using QROFN as a reference implementation, developers can follow the established patterns to integrate new fuzzy number types seamlessly.

The extension development process follows three essential phases: Implementation (creating type-specific algorithms in modular ext/ files), Registration (using @extension decorators with appropriate parameters), and Integration (calling apply_extensions() to inject methods into core classes).

Key implementation requirements include 22 core extension methods spanning constructors, I/O operations, mathematical functions, measurements, and properties. The Register-Dispatch-Inject architecture ensures type-safe polymorphic behavior while maintaining optimal performance through backend component array access and vectorized operations.

Successful extension development requires adherence to AxisFuzzy’s architectural principles: modular organization, comprehensive error handling, performance optimization, and thorough testing. The apply_extensions() function serves as the critical integration point, transforming individual implementations into accessible instance methods and top-level functions.

Warning

Extension methods directly modify core class behavior. Comprehensive testing and validation are mandatory before production deployment.