Fuzzy Types Extension

This development guide provides comprehensive instructions for extending AxisFuzzy with custom fuzzy number types. The guide demonstrates the complete implementation process using Q-Rung Orthopair Fuzzy Numbers (QROFN) as a reference example, covering both the strategy pattern implementation and backend architecture.

The Fuzzy Types Extension Framework

AxisFuzzy employs a sophisticated architecture that separates the user interface layer from the computational backend through the Strategy Pattern. This design enables developers to create new fuzzy number types by implementing two core components:

  1. FuzznumStrategy Subclass: Defines the mathematical properties, validation rules, and user interface behavior

  2. FuzzarrayBackend Subclass: Implements the high-performance computational backend using Structure-of-Arrays (SoA) architecture

This modular approach ensures that new fuzzy types integrate seamlessly with AxisFuzzy’s existing operation framework while maintaining optimal performance characteristics.

Creating FuzznumStrategy Subclass

The foundation of any custom fuzzy number type in AxisFuzzy is the FuzznumStrategy subclass. This section demonstrates how to implement a complete strategy class using the Q-Rung Orthopair Fuzzy Number (QROFN) as our reference example.

Conceptual Foundation

The FuzznumStrategy abstract base class provides the core infrastructure for single-element fuzzy number implementations. It enforces a strict attribute declaration contract, provides validation frameworks, and offers operation dispatch capabilities with caching support.

Basic Structure Example:

from typing import Optional, Any
import numpy as np
from axisfuzzy.core import FuzznumStrategy, register_strategy
from axisfuzzy.config import get_config

@register_strategy
class QROFNStrategy(FuzznumStrategy):
    """Q-Rung Orthopair Fuzzy Number Strategy Implementation."""

    # Attribute declarations using type annotations
    mtype = 'qrofn'
    md: Optional[float] = None
    nmd: Optional[float] = None

Attribute Declaration System

AxisFuzzy uses type annotations and class-level assignments for attribute declarations. The framework automatically collects these declarations during class creation via __init_subclass__.

Attribute Declaration Patterns:

class QROFNStrategy(FuzznumStrategy):
    # Core fuzzy components with type annotations
    mtype = 'qrofn'
    md: Optional[float] = None
    nmd: Optional[float] = None

    # Inherited from base class
    q: Optional[int] = None

Initialization and Validator Registration:

def __init__(self, q: Optional[int] = None):
    super().__init__(q=q)

    # Register attribute validators using lambda functions
    self.add_attribute_validator(
        'md', lambda x: x is None or isinstance(x, (int, float, np.floating, np.integer)) and 0 <= x <= 1)
    self.add_attribute_validator(
        'nmd', lambda x: x is None or isinstance(x, (int, float, np.floating, np.integer)) and 0 <= x <= 1)

Validation Framework Implementation

The validation framework operates through the add_attribute_validator() method, which registers callable validators that are automatically invoked during attribute assignment.

Validator Registration Pattern:

def __init__(self, q: Optional[int] = None):
    super().__init__(q=q)

    # Range validation for membership degree
    self.add_attribute_validator(
        'md', lambda x: x is None or isinstance(x, (int, float, np.floating, np.integer)) and 0 <= x <= 1)

    # Range validation for non-membership degree
    self.add_attribute_validator(
        'nmd', lambda x: x is None or isinstance(x, (int, float, np.floating, np.integer)) and 0 <= x <= 1)

Constraint Implementation Patterns

Complex fuzzy number types often require cross-attribute constraints. The QROFN constraint \(\mu^q + \nu^q \leq 1\) exemplifies this pattern.

Instance-Level Constraint Validation:

def _fuzz_constraint(self):
    """Validate q-rung orthopair constraints."""
    if self.md is not None and self.nmd is not None and self.q is not None:
        sum_of_powers = self.md ** self.q + self.nmd ** self.q
        if sum_of_powers > 1 + get_config().DEFAULT_EPSILON:
            raise ValueError(
                f"violates fuzzy number constraints: "
                f"md^q ({self.md}^{self.q}) + nmd^q ({self.nmd}^{self.q})"
                f"={sum_of_powers: .4f} > 1.0."
                f"(q: {self.q}, md: {self.md}, nmd: {self.nmd})")

Override Base Validation Method:

def _validate(self) -> None:
    """Override base validation to include fuzzy constraints."""
    super()._validate()
    self._fuzz_constraint()

Change Callbacks and Reactive Behavior

The framework supports reactive programming patterns through change callbacks registered via add_change_callback() that trigger when attribute values are modified.

Implementing Change Callbacks:

def __init__(self, q: Optional[int] = None):
    super().__init__(q=q)

    # Register validators first
    self.add_attribute_validator('md', lambda x: x is None or isinstance(x, (int, float, np.floating, np.integer)) and 0 <= x <= 1)
    self.add_attribute_validator('nmd', lambda x: x is None or isinstance(x, (int, float, np.floating, np.integer)) and 0 <= x <= 1)

    # Register change callbacks
    self.add_change_callback('md', self._on_membership_change)
    self.add_change_callback('nmd', self._on_membership_change)
    self.add_change_callback('q', self._on_q_change)

Callback Implementation:

def _on_membership_change(self, attr_name: str, old_value: Any, new_value: Any) -> None:
    """Callback triggered when membership or non-membership degree changes."""
    if new_value is not None and self.q is not None and hasattr(self, 'md') and hasattr(self, 'nmd'):
        self._fuzz_constraint()

def _on_q_change(self, attr_name: str, old_value: Any, new_value: Any) -> None:
    """Callback triggered when q parameter changes."""
    if self.md is not None and self.nmd is not None and new_value is not None:
        self._fuzz_constraint()

Formatting and String Representation

Proper string representation is crucial for debugging and user interaction. The framework provides multiple formatting options through specific methods.

Standard Representation Methods:

def format_from_components(self, md: float, nmd: float, format_spec: str = "") -> str:
    """Format fuzzy number from component values."""
    if md is None and nmd is None:
        return "<>"
    precision = get_config().DEFAULT_PRECISION
    if format_spec == 'p':
        return f"({md}, {nmd})"
    if format_spec == 'j':
        import json
        return json.dumps({'mtype': self.mtype, 'md': md, 'nmd': nmd, 'q': self.q})

    def strip_trailing_zeros(x: float) -> str:
        s = f"{x:.{precision}f}".rstrip('0').rstrip('.')
        return s if s else "0"

    md_str = strip_trailing_zeros(md)
    nmd_str = strip_trailing_zeros(nmd)
    return f"<{md_str},{nmd_str}>"

def report(self) -> str:
    """Generate report representation."""
    return self.format_from_components(self.md, self.nmd)

def str(self) -> str:
    """Generate string representation."""
    return self.format_from_components(self.md, self.nmd)

def __format__(self, format_spec: str) -> str:
    """Custom formatting support."""
    if format_spec and format_spec not in ['r', 'p', 'j']:
        return format(self.str(), format_spec)
    return self.format_from_components(self.md, self.nmd, format_spec)

Implementing FuzzarrayBackend

The FuzzarrayBackend provides high-performance array storage using the Struct-of-Arrays (SoA) architecture. This section demonstrates how to implement a concrete backend for your custom fuzzy number type, using QROFNBackend as the reference implementation.

SoA Architecture Design

The SoA pattern separates fuzzy number components into independent NumPy arrays, enabling vectorized operations and memory-efficient storage. For QROFN, this means storing membership degrees and non-membership degrees in separate arrays.

from typing import Any, Tuple, Optional, Callable
import numpy as np
from axisfuzzy.core import FuzzarrayBackend, register_backend

@register_backend
class QROFNBackend(FuzzarrayBackend):
    """SoA backend for q-rung orthopair fuzzy numbers."""

    mtype = 'qrofn'

    def __init__(self, shape: Tuple[int, ...], q: Optional[int] = None, **kwargs):
        super().__init__(shape, q, **kwargs)

The backend inherits from FuzzarrayBackend and must define the mtype class attribute to identify the fuzzy number type it supports.

Component Properties

Define the structural properties that describe your fuzzy number’s components:

@property
def cmpnum(self) -> int:
    """Number of components (2 for QROFN: md, nmd)."""
    return 2

@property
def cmpnames(self) -> Tuple[str, ...]:
    """Component names for display and access."""
    return 'md', 'nmd'

@property
def dtype(self) -> np.dtype:
    """Data type for component arrays."""
    return np.dtype(np.float64)

These properties inform the framework about your fuzzy number’s structure and enable proper array initialization and element formatting.

Array Initialization

Implement _initialize_arrays to create the component storage arrays:

def _initialize_arrays(self):
    """Initialize membership and non-membership degree arrays."""
    self.mds = np.zeros(self.shape, dtype=np.float64)
    self.nmds = np.zeros(self.shape, dtype=np.float64)

The method creates NumPy arrays with the backend’s shape and appropriate data type. Each component gets its own array for optimal memory layout and vectorization.

Element Access Methods

Implement bidirectional conversion between array elements and Fuzznum objects:

def get_fuzznum_view(self, index: Any) -> 'Fuzznum':
    """Create a Fuzznum object from array data at the given index."""
    md_value = float(self.mds[index])
    nmd_value = float(self.nmds[index])

    return Fuzznum(mtype=self.mtype, q=self.q).create(md=md_value, nmd=nmd_value)

def set_fuzznum_data(self, index: Any, fuzznum: 'Fuzznum'):
    """Set array data from a Fuzznum object at the given index."""
    if fuzznum.mtype != self.mtype:
        raise ValueError(f"Mtype mismatch: expected {self.mtype}, got {fuzznum.mtype}")

    if fuzznum.q != self.q:
        raise ValueError(f"Q parameter mismatch: expected {self.q}, got {fuzznum.q}")

    self.mds[index] = fuzznum.md
    self.nmds[index] = fuzznum.nmd

These methods handle the conversion between the high-level Fuzznum interface and the low-level array storage, including proper type validation.

Memory Management

Implement efficient copying and slicing operations:

def copy(self) -> 'QROFNBackend':
    """Create a deep copy of the backend."""
    new_backend = QROFNBackend(self.shape, self.q, **self.kwargs)
    new_backend.mds = self.mds.copy()
    new_backend.nmds = self.nmds.copy()
    return new_backend

def slice_view(self, key) -> 'QROFNBackend':
    """Create a view of the backend with the given slice."""
    new_shape = self.mds[key].shape
    new_backend = QROFNBackend(new_shape, self.q, **self.kwargs)
    new_backend.mds = self.mds[key]
    new_backend.nmds = self.nmds[key]
    return new_backend

The copy method creates independent copies for safe mutation, while slice_view creates memory-efficient views that share data with the original backend.

Performance Optimization

Implement formatting methods for efficient display operations:

def _get_element_formatter(self, format_spec: str) -> Callable:
    """Get element formatting function based on format specification."""
    precision = get_config().DEFAULT_PRECISION

    if format_spec in ('p', 'j', 'r'):
        # Use Strategy formatting for special formats
        def strategy_formatter(md: float, nmd: float) -> str:
            fuzznum = Fuzznum(mtype=self.mtype, q=self.q).create(md=md, nmd=nmd)
            return fuzznum.format(format_spec)
        return strategy_formatter
    else:
        # Use default numeric formatting
        return self._create_default_formatter(precision)

def _format_single_element(self, index: Any, formatter: Callable, format_spec: str) -> str:
    """Format a single element using the provided formatter."""
    md_val = self.mds[index]
    nmd_val = self.nmds[index]
    return formatter(md_val, nmd_val)

The formatting system delegates to the FuzznumStrategy for complex formats while providing efficient numeric formatting for simple cases.

Factory Methods

Implement class methods for convenient backend creation:

@classmethod
def from_arrays(cls, mds: np.ndarray, nmds: np.ndarray, q: int, **kwargs) -> 'QROFNBackend':
    """Create backend from existing NumPy arrays."""
    if mds.shape != nmds.shape:
        raise ValueError("Membership and non-membership arrays must have the same shape")

    backend = cls(mds.shape, q, **kwargs)
    backend.mds = mds
    backend.nmds = nmds
    return backend

def fill_from_values(self, md_value: float, nmd_value: float):
    """Fill all elements with the specified values."""
    self.mds.fill(md_value)
    self.nmds.fill(nmd_value)
These factory methods provide convenient ways to create and populate backend instances

from various data sources, supporting different initialization patterns.

Registration and Integration

The final step in implementing a custom fuzzy number type is registering both the strategy and backend with AxisFuzzy’s type system. This section demonstrates the registration process using the decorator system and validates successful integration.

Strategy Registration

Use the register_strategy() decorator to register your FuzznumStrategy subclass:

from axisfuzzy.core import FuzznumStrategy, register_strategy

@register_strategy
class QROFNStrategy(FuzznumStrategy):
    """Strategy for q-rung orthopair fuzzy numbers."""

    mtype = 'qrofn'

    def __init__(self, q: Optional[int] = None):
        super().__init__(q)
        # Add attribute validators and change callbacks
        self.add_attribute_validator('md', self._validate_membership)
        self.add_attribute_validator('nmd', self._validate_non_membership)
        self.add_change_callback('md', self._on_membership_change)
        self.add_change_callback('nmd', self._on_membership_change)

The decorator automatically registers the strategy with the global registry when the class is defined. The mtype attribute must match between strategy and backend implementations.

Backend Registration

Similarly, use the register_backend() decorator for your FuzzarrayBackend subclass:

from axisfuzzy.core import FuzzarrayBackend, register_backend

@register_backend
class QROFNBackend(FuzzarrayBackend):
    """SoA backend for q-rung orthopair fuzzy numbers."""

    mtype = 'qrofn'

    def __init__(self, shape: Tuple[int, ...], q: Optional[int] = None, **kwargs):
        super().__init__(shape, q, **kwargs)

The registration process validates that the backend class properly implements all required abstract methods and has a valid mtype attribute.

Type Validation

The registry system performs comprehensive validation during registration:

# Automatic validation checks performed by decorators:
# 1. mtype attribute presence and validity
# 2. Required method implementations
# 3. Class inheritance from correct base classes
# 4. No conflicts with existing registrations

# Manual validation example:
from axisfuzzy.core.registry import get_registry_fuzztype

registry = get_registry_fuzztype()

# Check if both strategy and backend are registered
registered_types = registry.get_registered_mtypes()
if 'qrofn' in registered_types:
    qrofn_info = registered_types['qrofn']
    print(f"Strategy registered: {qrofn_info['has_strategy']}")
    print(f"Backend registered: {qrofn_info['has_backend']}")
    print(f"Complete type: {qrofn_info['is_complete']}")

The registry ensures type consistency and prevents registration conflicts that could compromise system stability.

Registry System

The FuzznumRegistry provides a centralized type management system:

# Access the global registry
from axisfuzzy.core.registry import get_registry_fuzztype

registry = get_registry_fuzztype()

# Query registered types
all_types = registry.get_registered_mtypes()
for mtype, info in all_types.items():
    print(f"{mtype}: Strategy={info['has_strategy']}, Backend={info['has_backend']}")

# Get specific implementations
qrofn_strategy = registry.get_strategy('qrofn')
qrofn_backend = registry.get_backend('qrofn')

# Registry statistics
stats = registry.get_statistics()
print(f"Total strategies: {stats['total_strategies']}")
print(f"Total backends: {stats['total_backends']}")
print(f"Complete types: {stats['complete_types']}")

The registry supports thread-safe operations and provides comprehensive introspection capabilities for debugging and system monitoring.

Integration Testing

Verify successful registration and functionality with comprehensive tests:

# Test 1: Verify registration
from axisfuzzy.core.registry import get_registry_fuzztype

registry = get_registry_fuzztype()
assert 'qrofn' in registry.get_registered_mtypes()

# Test 2: Create Fuzznum instances
from axisfuzzy.core import Fuzznum

# Single fuzzy number creation
qrofn = Fuzznum(mtype='qrofn', q=2).create(md=0.8, nmd=0.3)
assert qrofn.mtype == 'qrofn'
assert qrofn.q == 2
assert qrofn.md == 0.8
assert qrofn.nmd == 0.3

# Test 3: Create Fuzzarray instances
from axisfuzzy.core import Fuzzarray
import numpy as np

# Array creation and manipulation
arr = Fuzzarray(mtype='qrofn', shape=(2, 3), q=2)
arr.backend.fill_from_values(0.7, 0.2)

# Verify array properties
assert arr.mtype == 'qrofn'
assert arr.shape == (2, 3)
assert arr.q == 2

# Test element access
element = arr[0, 0]
assert isinstance(element, Fuzznum)
assert element.md == 0.7
assert element.nmd == 0.2

These tests ensure that your custom fuzzy number type integrates properly with AxisFuzzy’s core functionality and can be used in all standard operations.

Complete Implementation

A complete fuzzy number type implementation requires both strategy and backend registration:

# File: my_fuzzy_type.py
from typing import Optional, Tuple, Any
import numpy as np
from axisfuzzy.core import (
    FuzznumStrategy, FuzzarrayBackend,
    register_strategy, register_backend
)

@register_strategy
class MyFuzzyStrategy(FuzznumStrategy):
    mtype = 'my_fuzzy'

    def __init__(self, q: Optional[int] = None):
        super().__init__(q)
        # Implementation details...

@register_backend
class MyFuzzyBackend(FuzzarrayBackend):
    mtype = 'my_fuzzy'

    def __init__(self, shape: Tuple[int, ...], q: Optional[int] = None, **kwargs):
        super().__init__(shape, q, **kwargs)
        # Implementation details...

# Automatic registration occurs when the module is imported
# Your fuzzy type is now available throughout AxisFuzzy

Once both components are registered, your custom fuzzy number type becomes available throughout the AxisFuzzy ecosystem for creation, manipulation, and computation.

Conclusion

This development guide provides a comprehensive framework for extending AxisFuzzy with custom fuzzy number types. The systematic approach outlined here ensures both mathematical correctness and seamless integration with the existing ecosystem.

Key Implementation Steps:

  1. Strategy Development: Implement FuzznumStrategy with proper attribute management, validation, and mathematical operations specific to your fuzzy type.

  2. Backend Architecture: Create FuzzarrayBackend following the SoA pattern for efficient array operations and memory management.

  3. Registration Integration: Use @register_strategy and @register_backend decorators to make your implementation discoverable throughout AxisFuzzy.

  4. Comprehensive Testing: Establish robust test suites covering unit tests, constraint validation, integration testing, performance benchmarks, and error handling.

Best Practices:

  • Maintain mathematical rigor in constraint validation and boundary condition handling

  • Follow the established patterns for attribute management and data access

  • Ensure performance characteristics meet production requirements

  • Implement comprehensive error handling for robust operation

By following this guide, developers can confidently extend AxisFuzzy’s capabilities while maintaining the library’s standards for correctness, performance, and usability. The modular architecture ensures that custom implementations integrate seamlessly with existing fuzzy logic operations and computational workflows.

Next Steps: After implementation, consider contributing your fuzzy type back to the AxisFuzzy community through the established contribution guidelines, enabling broader adoption and collaborative improvement of your mathematical model.