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:
Type-based Polymorphism: Extensions are dispatched based on the
mtypeattribute of fuzzy objects, enabling specialized implementations for different fuzzy number types (e.g.,qrofn,ifn,pfn).Declarative Registration: The
@extensiondecorator provides a clean, declarative API for registering extension functions without requiring manual registry manipulation.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 automaticmtyperesolution fromobjInstance Property Proxies: Accessed as
obj.propertyfor read-only computed attributesTop-level Function Proxies: Callable as
axisfuzzy.function(obj, ...)withmtyperesolution 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:
Scan registry metadata to determine target classes and injection types
Create appropriate dispatcher proxies for each extension
Attach proxies to
Fuzznum,Fuzzarrayclasses oraxisfuzzymodule namespaceAvoid 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:
Exact Match: Look for implementation registered with specific
mtypeDefault Fallback: Use implementation registered with
mtype=Noneif availableError: 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:
Method Name |
Category |
Injection Type |
Purpose |
|---|---|---|---|
|
Constructor |
Top-level Function |
Create uninitialized QROFN objects |
|
Constructor |
Top-level Function |
Create objects with maximum membership (md=1, nmd=0) |
|
Constructor |
Top-level Function |
Create objects with maximum non-membership (md=0, nmd=1) |
|
Constructor |
Top-level Function |
Create objects filled with specific values |
|
Constructor |
Top-level Function |
Create uninitialized objects matching input shape |
|
Constructor |
Top-level Function |
Create positive objects matching input shape |
|
Constructor |
Top-level Function |
Create negative objects matching input shape |
|
Constructor |
Top-level Function |
Create filled objects matching input shape |
|
I/O Operations |
Instance Method |
Export fuzzy arrays to CSV format |
|
I/O Operations |
Top-level Function |
Import fuzzy arrays from CSV files |
|
I/O Operations |
Instance Method |
Export fuzzy arrays to JSON format |
|
I/O Operations |
Top-level Function |
Import fuzzy arrays from JSON files |
|
I/O Operations |
Instance Method |
Export fuzzy arrays to NumPy binary format |
|
I/O Operations |
Top-level Function |
Import fuzzy arrays from NumPy binary files |
|
Mathematical |
Instance Method |
Aggregate using t-conorm reduction |
|
Mathematical |
Instance Method |
Calculate fuzzy arithmetic mean |
|
Mathematical |
Instance Method |
Find maximum based on score function |
|
Mathematical |
Instance Method |
Find minimum based on score function |
|
Mathematical |
Instance Method |
Aggregate using t-norm reduction |
|
Mathematical |
Instance Method |
Calculate fuzzy variance |
|
Mathematical |
Instance Method |
Calculate fuzzy standard deviation |
|
Measurement |
Instance Method |
Compute distance between fuzzy objects |
|
Property |
Instance Property |
Calculate membership score (md^q - nmd^q) |
|
Property |
Instance Property |
Calculate accuracy measure |
|
Property |
Instance Property |
Calculate indeterminacy degree |
|
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:
Mathematical Consistency: Ensure all operations respect the mathematical constraints of your fuzzy type
Backend Integration: Utilize the backend’s component array access for optimal performance
Axis-Aware Operations: Support axis-specific reductions for multi-dimensional arrays
Fallback Handling: Provide graceful degradation for edge cases (empty arrays, single elements)
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:
Parameter |
Type |
Description |
|---|---|---|
|
str (required) |
The method/function name as it will appear to users |
|
str or None |
Target fuzzy type (‘qrofn’, ‘ivfs’, etc.). None for default implementations |
|
List[str] |
Classes to inject into: [‘Fuzznum’], [‘Fuzzarray’], or [‘Fuzznum’, ‘Fuzzarray’] |
|
str |
How to expose: ‘instance_method’, ‘instance_property’, ‘top_level_function’, or ‘both’ |
|
bool |
Whether this serves as a fallback implementation for unspecified mtypes |
|
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_classesto 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:
Dynamic Class Discovery: Locates
FuzznumandFuzzarrayclasses at runtime to avoid circular importsModule Namespace Resolution: Identifies the
axisfuzzymodule for top-level function injectionExtension Injection: Delegates to
ExtensionInjectorto attach all registered extensionsIdempotency 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
Use descriptive names: Choose names that clearly indicate functionality
Add docstrings: Document parameters, returns, and examples
Handle errors: Check inputs and provide meaningful error messages
Test thoroughly: Test with different fuzzy types and edge cases
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.
Note
For foundational concepts, refer to Core Data Structures: Fuzznum and Fuzzarray and Extension and Mixin Systems: Extending AxisFuzzy Functionality documentation.
Warning
Extension methods directly modify core class behavior. Comprehensive testing and validation are mandatory before production deployment.