Random Generators Development

This development guide provides comprehensive instructions for implementing custom random generators 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, and integration workflows.

Understanding the Random Generator

AxisFuzzy’s random generation system follows a plugin-based architecture that enables developers to create specialized random generators for custom fuzzy number types. The system is built around three core principles:

  1. Type Specialization: Each fuzzy number type (mtype) has its own dedicated random generator

  2. High Performance: Vectorized batch generation optimized for large-scale Fuzzarray creation

  3. Unified Interface: Consistent API across all generator implementations through abstract base classes

The random generation framework consists of two main abstract base classes:

  • BaseRandomGenerator: Core interface defining the essential methods

  • ParameterizedRandomGenerator: Enhanced base class with distribution sampling utilities

This modular design ensures that custom generators integrate seamlessly with AxisFuzzy’s existing random generation API while maintaining optimal performance characteristics.

Architecture and Interface Design

The AxisFuzzy random generation framework provides a robust architecture for implementing custom fuzzy number generators. This section introduces the core interfaces and design patterns that enable seamless integration with the framework’s high-performance random generation system.

Understanding the Generator Hierarchy

The framework defines two primary abstract base classes that form the foundation of all random generators. Understanding their roles and relationships is essential for implementing custom generators.

BaseRandomGenerator Interface

The BaseRandomGenerator class establishes the fundamental contract that all generators must fulfill. This interface ensures consistency across different fuzzy number types while maintaining flexibility for type-specific implementations.

from axisfuzzy.random.base import BaseRandomGenerator

class CustomGenerator(BaseRandomGenerator):
    # Type identifier for the fuzzy number type
    mtype = "custom_type"

    def get_default_parameters(self) -> Dict[str, Any]:
        """Return default parameter values for generation."""
        return {'param1': 1.0, 'param2': 0.5}

    def validate_parameters(self, **params) -> None:
        """Validate parameter values before generation."""
        # Implementation-specific validation logic
        pass

    def fuzznum(self, rng: np.random.Generator, **params) -> 'Fuzznum':
        """Generate a single fuzzy number instance."""
        # Single instance generation logic
        pass

    def fuzzarray(self, rng: np.random.Generator,
                 shape: Tuple[int, ...], **params) -> 'Fuzzarray':
        """Generate a batch of fuzzy numbers with vectorized operations."""
        # High-performance batch generation logic
        pass

ParameterizedRandomGenerator Enhancement

The ParameterizedRandomGenerator extends the base interface with utilities for parameter management and distribution sampling. This class provides common functionality that most generators require, reducing implementation complexity.

from axisfuzzy.random.base import ParameterizedRandomGenerator

class EnhancedGenerator(ParameterizedRandomGenerator):
    mtype = "enhanced_type"

    def get_default_parameters(self) -> Dict[str, Any]:
        return {
            'low': 0.0, 'high': 1.0, 'dist': 'uniform',
            'a': 2.0, 'b': 5.0  # Beta distribution parameters
        }

    def validate_parameters(self, **params) -> None:
        # Leverage built-in validation utilities
        self._validate_range('low', params['low'], 0.0, 1.0)
        self._validate_range('high', params['high'], 0.0, 1.0)

Parameter Management Framework

Effective parameter management ensures robust and user-friendly generators. The framework provides standardized patterns for parameter declaration, validation, and merging.

Parameter Declaration Patterns

Parameters should be declared with meaningful defaults and comprehensive validation. The framework supports both simple range validation and complex cross-parameter constraints.

def get_default_parameters(self) -> Dict[str, Any]:
    """Define comprehensive parameter set with sensible defaults."""
    return {
        # Core generation parameters
        'md_low': 0.0, 'md_high': 1.0, 'md_dist': 'uniform',
        'nu_low': 0.0, 'nu_high': 1.0, 'nu_dist': 'uniform',

        # Distribution-specific parameters
        'a': 2.0, 'b': 5.0,  # Beta distribution shape parameters
        'loc': 0.0, 'scale': 1.0,  # Normal distribution parameters

        # Generation mode controls
        'nu_mode': 'orthopair'  # 'orthopair' or 'independent'
    }

Validation Implementation Strategy

Parameter validation should occur at multiple levels: individual parameter ranges, cross-parameter consistency, and mathematical constraint satisfaction.

def validate_parameters(self, q: int, **kwargs) -> None:
    """Implement comprehensive parameter validation."""
    params = self._merge_parameters(**kwargs)

    # Range validation using framework utilities
    self._validate_range('md_low', params['md_low'], 0.0, 1.0)
    self._validate_range('md_high', params['md_high'], 0.0, 1.0)

    # Cross-parameter consistency checks
    if params['md_low'] >= params['md_high']:
        raise ValueError("md_low must be less than md_high")

    # Distribution parameter validation
    if params['md_dist'] == 'beta' and (params['a'] <= 0 or params['b'] <= 0):
        raise ValueError("Beta distribution requires positive shape "
                       "parameters")

Distribution Sampling Integration

The framework integrates seamlessly with NumPy’s random generation system, providing built-in sampling utilities for common distributions while maintaining extensibility for custom sampling strategies.

Leveraging Built-in Sampling Utilities

The ParameterizedRandomGenerator provides the _sample_from_distribution method that supports multiple probability distributions with consistent parameter interfaces.

def fuzznum(self, rng: np.random.Generator, **params) -> 'Fuzznum':
    """Generate single instance using distribution sampling."""
    params = self._merge_parameters(**params)

    # Sample membership degree using specified distribution
    md = self._sample_from_distribution(
        rng, size=None, dist=params['md_dist'],
        low=params['md_low'], high=params['md_high'],
        a=params['a'], b=params['b'],
        loc=params['loc'], scale=params['scale']
    )

    # Apply mathematical constraints for valid fuzzy numbers
    # ... constraint application logic

    return Fuzznum(mtype=self.mtype).create(md=md, **other_components)

High-Performance Vectorized Generation

For batch generation, the framework emphasizes vectorized operations that avoid creating intermediate objects and leverage NumPy’s optimized array operations.

def fuzzarray(self, rng: np.random.Generator,
             shape: Tuple[int, ...], **params) -> 'Fuzzarray':
    """Implement high-performance batch generation."""
    params = self._merge_parameters(**params)
    size = int(np.prod(shape))

    # Vectorized sampling for all components
    mds = self._sample_from_distribution(
        rng, size=size, dist=params['md_dist'],
        low=params['md_low'], high=params['md_high'],
        a=params['a'], b=params['b']
    )

    # Apply vectorized constraints and reshape
    mds = mds.reshape(shape)

    # Create backend directly from arrays (avoid intermediate objects)
    backend = CustomBackend.from_arrays(mds=mds, **other_arrays)
    return Fuzzarray(backend=backend)

NumPy Integration and Performance Considerations

The framework’s integration with NumPy’s random generation system ensures reproducibility, performance, and compatibility with the broader scientific Python ecosystem.

Random Number Generator Management

All generation methods receive a np.random.Generator instance, ensuring proper seed management and statistical independence across different generation calls.

# Framework handles RNG lifecycle automatically
def fuzznum(self, rng: np.random.Generator, **params) -> 'Fuzznum':
    # Use provided RNG for all random operations
    sample = rng.uniform(low=0.0, high=1.0)
    # Never create new RNG instances within generators

Performance Optimization Guidelines

  • Vectorization: Use NumPy array operations instead of Python loops

  • Memory Efficiency: Avoid creating intermediate Fuzznum objects in batch generation

  • Backend Integration: Populate backend arrays directly for optimal performance

  • Constraint Application: Apply mathematical constraints using vectorized operations

QROFN Random Generator Implementation

This section demonstrates the complete implementation of a QROFN random generator, serving as a practical example for developing custom fuzzy number generators. The QROFN generator showcases advanced features including orthopair constraint handling, multiple distribution support, and high-performance vectorized generation.

Class Structure and Registration

The QROFN generator implementation begins with proper class declaration and automatic registration with the framework’s generator registry.

from axisfuzzy.random import register_random
from axisfuzzy.random.base import ParameterizedRandomGenerator
from axisfuzzy.core import Fuzznum, Fuzzarray
from .backend import QROFNBackend

@register_random
class QROFNRandomGenerator(ParameterizedRandomGenerator):
    """
    Random generator for Q-Rung Orthopair Fuzzy Numbers (QROFNs).

    Supports multiple probability distributions and orthopair constraint
    handling for generating mathematically valid QROFN instances.
    """
    mtype = "qrofn"

The @register_random decorator automatically registers the generator with the global registry, enabling access through the unified axisfuzzy.random.rand() interface.

Parameter Definition and Default Values

QROFN generation requires comprehensive parameter management to support flexible distribution sampling and constraint handling modes.

def get_default_parameters(self) -> Dict[str, Any]:
    """Define comprehensive parameter set for QROFN generation."""
    return {
        # Membership degree (μ) distribution parameters
        'md_dist': 'uniform', 'md_low': 0.0, 'md_high': 1.0,

        # Non-membership degree (ν) distribution parameters
        'nu_mode': 'orthopair',  # 'orthopair' or 'independent'
        'nu_dist': 'uniform', 'nu_low': 0.0, 'nu_high': 1.0,

        # Distribution-specific shape parameters
        'a': 2.0, 'b': 2.0,  # Beta distribution parameters
        'loc': 0.5, 'scale': 0.15,  # Normal distribution parameters
    }

The parameter set supports multiple probability distributions (uniform, beta, normal) and provides two constraint handling modes: orthopair mode ensures mathematical validity by dynamically adjusting sampling ranges, while independent mode samples components independently and applies post-generation constraint enforcement.

Validation Logic Implementation

Parameter validation ensures mathematical consistency and prevents invalid generation configurations that could produce malformed QROFN instances.

def validate_parameters(self, q: int, **kwargs) -> None:
    """Implement comprehensive parameter validation for QROFN generation."""
    params = self._merge_parameters(**kwargs)

    # Range validation for membership and non-membership bounds
    self._validate_range('md_low', params['md_low'], 0.0, 1.0)
    self._validate_range('md_high', params['md_high'], 0.0, 1.0)
    self._validate_range('nu_low', params['nu_low'], 0.0, 1.0)
    self._validate_range('nu_high', params['nu_high'], 0.0, 1.0)

    # Cross-parameter consistency validation
    if params['md_low'] >= params['md_high']:
        raise ValueError("md_low must be less than md_high")
    if params['nu_low'] >= params['nu_high']:
        raise ValueError("nu_low must be less than nu_high")

    # Distribution-specific parameter validation
    if params['md_dist'] == 'beta' and (params['a'] <= 0 or params['b'] <= 0):
        raise ValueError("Beta distribution requires positive shape "
                       "parameters")

    # Constraint mode validation
    if params['nu_mode'] not in ['orthopair', 'independent']:
        raise ValueError("nu_mode must be 'orthopair' or 'independent'")

Single Instance Generation (fuzznum method)

The fuzznum method demonstrates constraint-aware generation for individual QROFN instances, showcasing the orthopair constraint \(\mu^q + \nu^q \leq 1\) handling.

def fuzznum(self, rng: np.random.Generator,
            q: Optional[int] = None, **kwargs) -> 'Fuzznum':
    """Generate a single QROFN instance with constraint handling."""
    params = self._merge_parameters(**kwargs)
    q = q if q is not None else get_config().DEFAULT_Q
    self.validate_parameters(q=q, **params)

    # Sample membership degree using specified distribution
    md = self._sample_from_distribution(
        rng, size=None, dist=params['md_dist'],
        low=params['md_low'], high=params['md_high'],
        a=params['a'], b=params['b'],
        loc=params['loc'], scale=params['scale']
    )

    # Handle non-membership degree based on constraint mode
    if params['nu_mode'] == 'orthopair':
        # Calculate maximum allowed non-membership degree
        max_nmd = (1 - md ** q) ** (1 / q)
        effective_high = min(params['nu_high'], max_nmd)

        # Sample within constrained range
        nmd_sample = self._sample_from_distribution(
            rng, size=None, dist=params['nu_dist'],
            low=0.0, high=1.0,
            a=params['a'], b=params['b'],
            loc=params['loc'], scale=params['scale']
        )
        nmd = params['nu_low'] + nmd_sample * (effective_high -
                                               params['nu_low'])
        nmd = max(nmd, params['nu_low'])

    else:  # 'independent' mode
        nmd = self._sample_from_distribution(
            rng, size=None, dist=params['nu_dist'],
            low=params['nu_low'], high=params['nu_high'],
            a=params['a'], b=params['b'],
            loc=params['loc'], scale=params['scale']
        )
        # Apply constraint enforcement if violated
        if (md ** q + nmd ** q) > 1.0:
            nmd = (1 - md ** q) ** (1 / q)

    return Fuzznum(mtype='qrofn', q=q).create(md=md, nmd=nmd)

High-Performance Batch Generation (fuzzarray method)

The fuzzarray method implements vectorized generation for optimal performance when creating large batches of QROFN instances.

def fuzzarray(self, rng: np.random.Generator,
              shape: Tuple[int, ...], q: Optional[int] = None,
              **params) -> 'Fuzzarray':
    """Generate QROFN batch using high-performance vectorized operations."""
    params = self._merge_parameters(**params)
    q = q if q is not None else get_config().DEFAULT_Q
    self.validate_parameters(q=q, **params)

    size = int(np.prod(shape))

    # Vectorized membership degree generation
    mds = self._sample_from_distribution(
        rng, size=size, dist=params['md_dist'],
        low=params['md_low'], high=params['md_high'],
        a=params['a'], b=params['b'],
        loc=params['loc'], scale=params['scale']
    )

    # Vectorized non-membership degree generation with constraint handling
    if params['nu_mode'] == 'orthopair':
        # Calculate element-wise maximum allowed non-membership degrees
        max_nmd = (1 - mds ** q) ** (1 / q)
        effective_high = np.minimum(params['nu_high'], max_nmd)

        # Sample and scale to dynamic ranges
        nmds = self._sample_from_distribution(
            rng, size=size, dist=params['nu_dist'],
            low=params['nu_low'], high=1.0,
            a=params['a'], b=params['b'],
            loc=params['loc'], scale=params['scale']
        )
        nmds = params['nu_low'] + nmds * (effective_high - params['nu_low'])
        nmds = np.maximum(nmds, params['nu_low'])

    else:  # 'independent' mode with vectorized constraint enforcement
        nmds = self._sample_from_distribution(
            rng, size=size, dist=params['nu_dist'],
            low=params['nu_low'], high=params['nu_high'],
            a=params['a'], b=params['b'],
            loc=params['loc'], scale=params['scale']
        )
        # Vectorized constraint enforcement
        violates_mask = (mds ** q + nmds ** q) > 1.0
        if np.any(violates_mask):
            max_nmd_violating = (1 - mds[violates_mask] ** q) ** (1 / q)
            nmds[violates_mask] = np.minimum(nmds[violates_mask],
                                             max_nmd_violating)

    # Reshape and create backend directly from arrays
    mds = mds.reshape(shape)
    nmds = nmds.reshape(shape)
    backend = QROFNBackend.from_arrays(mds=mds, nmds=nmds, q=q)

    return Fuzzarray(backend=backend)

Mathematical Constraint Handling

The QROFN implementation demonstrates sophisticated constraint handling that maintains mathematical validity while supporting flexible generation modes.

Orthopair Mode: Dynamically calculates valid sampling ranges based on the constraint \(\mu^q + \nu^q \leq 1\), ensuring all generated instances satisfy the mathematical requirements without post-generation rejection.

Independent Mode: Allows independent sampling followed by constraint enforcement, providing greater flexibility for specific distribution requirements while maintaining mathematical validity through post-generation adjustment.

Registration and Integration

This section demonstrates how to integrate your custom random generator into AxisFuzzy’s global registry system, enabling seamless access through the unified API. The QROFN generator serves as our reference implementation.

Automatic Registration with Decorators

AxisFuzzy provides the @register_random decorator for automatic generator registration. This decorator handles all registration logic and validation:

from axisfuzzy.random import register_random
from axisfuzzy.random.base import ParameterizedRandomGenerator

@register_random
class QROFNRandomGenerator(ParameterizedRandomGenerator):
    """
    High-performance random generator for q-rung orthopair fuzzy numbers.
    """
    mtype = "qrofn"  # Required: unique identifier

    def get_default_parameters(self) -> Dict[str, Any]:
        return {
            'md_dist': 'uniform',
            'md_low': 0.0,
            'md_high': 1.0,
            'nu_mode': 'orthopair',
            # ... other parameters
        }

    # Implementation methods...

The decorator performs several critical operations:

  1. Type Validation: Ensures the class inherits from BaseRandomGenerator

  2. mtype Registration: Maps the generator’s mtype to the class instance

  3. Singleton Creation: Instantiates and registers a single generator instance

  4. Thread Safety: Uses locks to ensure safe concurrent registration

Registry System Integration

Once registered, generators become accessible through AxisFuzzy’s unified API. The registry system provides several access patterns:

from axisfuzzy.random.registry import (
    get_random_generator,
    list_registered_random,
    is_registered_random
)

# Check if generator is available
if is_registered_random('qrofn'):
    generator = get_random_generator('qrofn')

# List all registered generators
available_types = list_registered_random()
print(f"Available generators: {available_types}")

The registry maintains thread-safe access to all generators and supports dynamic registration during runtime.

API Integration and Usage

Registered generators automatically integrate with the high-level API functions:

import axisfuzzy.random as fr

# Single fuzzy number generation
num = fr.rand('qrofn', q=2)

# Array generation with custom parameters
arr = fr.rand('qrofn',
              shape=(1000, 100),
              q=3,
              md_dist='beta',
              a=2.0, b=5.0,
              nu_mode='orthopair')

# Seeded generation for reproducibility
reproducible_arr = fr.rand('qrofn',
                           shape=(500,),
                           q=2,
                           seed=42)

Testing and Validation Workflows

Comprehensive testing ensures generator reliability and performance. Key testing areas include:

Parameter Validation Testing:

def test_parameter_validation():
    generator = get_random_generator('qrofn')

    # Test valid parameters
    generator.validate_parameters(q=2, md_low=0.0, md_high=1.0)

    # Test invalid parameters (should raise exceptions)
    with pytest.raises(ValueError):
        generator.validate_parameters(q=0)  # Invalid q value

Output Correctness Testing:

def test_output_constraints():
    arr = fr.rand('qrofn', shape=(1000,), q=2, seed=42)

    # Verify mathematical constraints
    assert np.all(arr.md >= 0) and np.all(arr.md <= 1)
    assert np.all(arr.nmd >= 0) and np.all(arr.nmd <= 1)
    assert np.all(arr.md**2 + arr.nmd**2 <= 1)  # q=2 constraint

Performance Benchmarking:

def benchmark_generation():
    import time

    sizes = [1000, 10000, 100000]
    for size in sizes:
        start = time.time()
        arr = fr.rand('qrofn', shape=(size,), q=2)
        duration = time.time() - start
        print(f"Generated {size} QROFNs in {duration:.4f}s")

Best Practices for Production Deployment

1. Error Handling: Implement robust error handling for edge cases:

def safe_generation(mtype, **params):
    try:
        if not is_registered_random(mtype):
            raise ValueError(f"Generator '{mtype}' not registered")
        return fr.rand(mtype, **params)
    except Exception as e:
        logger.error(f"Generation failed: {e}")
        return None

2. Parameter Caching: Cache validated parameters for repeated operations:

class CachedGenerator:
    def __init__(self, mtype):
        self.generator = get_random_generator(mtype)
        self._param_cache = {}

    def generate(self, **params):
        param_key = tuple(sorted(params.items()))
        if param_key not in self._param_cache:
            self.generator.validate_parameters(**params)
            self._param_cache[param_key] = params
        return self.generator.fuzzarray(rng, **params)

3. Memory Management: For large-scale generation, consider memory-efficient patterns:

def generate_batches(total_size, batch_size=10000, **params):
    """Generate large arrays in batches to manage memory usage."""
    batches = []
    for i in range(0, total_size, batch_size):
        current_batch_size = min(batch_size, total_size - i)
        batch = fr.rand('qrofn', shape=(current_batch_size,), **params)
        batches.append(batch)
    return np.concatenate(batches)

Conclusion

This guide has demonstrated the complete workflow for developing custom random generators in AxisFuzzy, using the QROFN generator as a comprehensive example. The development process follows a structured three-phase approach:

Phase 1: Architecture and Interface Design establishes the foundation through proper inheritance from ParameterizedRandomGenerator, comprehensive parameter management, and integration with AxisFuzzy’s distribution sampling framework.

Phase 2: Implementation focuses on core generation logic, including robust parameter validation, efficient single-instance and batch generation methods, and proper handling of mathematical constraints specific to your fuzzy number type.

Phase 3: Registration and Integration ensures seamless integration with AxisFuzzy’s ecosystem through automatic registration, comprehensive testing workflows, and production-ready deployment practices.

Key Design Principles:

  • Modularity: Each generator is self-contained with clear interfaces

  • Performance: Vectorized operations and efficient memory management

  • Extensibility: Plugin-style architecture supports diverse fuzzy number types

  • Reliability: Comprehensive validation and error handling throughout

Recommendations for Extension:

  1. Follow the established patterns demonstrated by existing generators

  2. Implement comprehensive parameter validation for your specific mathematical constraints

  3. Optimize for vectorized operations to handle large-scale generation efficiently

  4. Include thorough testing covering edge cases and performance benchmarks

  5. Document your generator’s mathematical foundations and usage patterns

By following this development framework, you can create robust, high-performance random generators that integrate seamlessly with AxisFuzzy’s unified API, contributing to the library’s extensible architecture while maintaining consistency and reliability across all fuzzy number types.