Random Generation System
The random generation system in axisfuzzy is a cornerstone for simulation,
analysis, and testing in fuzzy logic applications. It provides a powerful and
flexible framework for creating stochastic fuzzy numbers, which is essential for
modeling uncertainty and variability in real-world systems. Whether you are
performing Monte Carlo simulations, generating synthetic data for machine learning
models, or testing the robustness of your fuzzy algorithms, this system offers the
tools you need for reproducible and statistically sound random generation.
This guide will walk you through the architecture and usage of the random generation system, covering everything from basic high-level APIs to the underlying mechanics of reproducibility and extensibility. You will learn how to:
Ensure reproducibility of your random experiments using a sophisticated seeding mechanism.
Leverage the high-level API to effortlessly generate various types of random fuzzy numbers.
Understand the core components of the system, including the generator blueprint and the discovery mechanism.
Extend the system by creating and registering your own custom random generators.
By the end of this guide, you will have a comprehensive understanding of how to
harness the full potential of axisfuzzy’s random generation capabilities.
Architectural Deep Dive
The axisfuzzy.random system is built upon a philosophy of modularity, extensibility,
and reproducibility. Its architecture is composed of four core components that work
in concert to provide a powerful yet easy-to-use framework for random fuzzy number
generation. Understanding this architecture is key to leveraging the system’s full
potential and extending it with custom functionality.
Design Philosophy
Modularity & Extensibility: The system is designed as a “plug-in” architecture. New random generators for different fuzzy membership types (
mtype) can be defined and registered without altering any core code. This ensures that the system can easily adapt to future fuzzy number representations.Reproducibility: Scientific computing demands reproducible results. A centralized seed management system ensures that the same seed will always produce the exact same sequence of random fuzzy numbers, which is critical for debugging, validation, and sharing experiments.
Unified API: Despite the complexity of the underlying implementations for different fuzzy types, the system exposes a simple and consistent high-level API (e.g.,
axisfuzzy.random.rand()). Users can generate any supported fuzzy type through the same interface, significantly lowering the barrier to entry.High Performance: For large-scale simulations, performance is paramount. The system leverages vectorized operations and interacts directly with the Struct of Arrays (SoA) backend of
Fuzzarray, avoiding inefficient Python-level loops and achieving performance close to that of NumPy.
Core Components
The system’s functionality is divided among four key modules:
┌─────────────────────────────────────────────────────────────────┐
│ User Layer (e.g., your script) │
├─────────────────────────────────────────────────────────────────┤
│ fr.rand() │ fr.choice() │ fr.uniform() │ fr.set_seed() │ ... │
└─────────────┬───────────────────────────────────────────────────┘
│
┌─────────────▼───────────────────────────────────────────────────┐
│ API Layer (api.py) │
│ • Unified entry points (rand, choice, etc.) │
│ • Parameter parsing and validation │
│ • Dispatches to the appropriate generator │
└─────────────┬───────────────────────────────────────────────────┘
│
┌─────────▼─────────┐ ┌──────────────────────┐
│ Registry Layer │ │ Seed Management │
│ (registry.py) │ │ (seed.py) │
│ │ │ │
│• Manages generator│◄─────────►│• Global random state │
│ registration │ │• RNG instantiation │
│• Maps `mtype` to │ │• Spawns independent │
│ generator │ │ streams for parallel│
└─────────┬─────────┘ └──────────────────────┘
│ │
┌─────────────▼───────────────────────────────▼───────────────────┐
│ Generator Layer (base.py) │
│ │
│ BaseRandomGenerator ParameterizedRandomGenerator │
│ ├─ Defines the common ├─ _merge_parameters() │
│ │ interface for all ├─ _sample_from_distribution() │
│ │ generators ├─ _validate_range() │
│ └─ (fuzznum, fuzzarray) └─ ... and other utilities │
└─────────────┬───────────────────────────────────────────────────┘
│
┌─────────────▼───────────────────────────────────────────────────┐
│ Concrete Implementation Layer │
│ │
│ QROFNRandomGenerator │ IVFNRandomGenerator │ ... │
│ ├─ mtype = "qrofn" │ ├─ mtype = "ivfn" │ │
│ ├─ Handles specific │ ├─ Implements interval-│ │
│ │ constraints │ │ based logic │ │
└─────────────────────────────────────────────────────────────────┘
The API Layer (api.py): This is the primary user-facing entry point. It provides the high-level functions like
rand()andchoice(). Its role is to interpret the user’s request (e.g.,mtype,shape, and other parameters) and coordinate the other components to fulfill it.The Registry Layer (registry.py): This acts as the system’s “dispatch center.” It maintains a mapping from an
mtypestring (like'qrofn') to a specific generator instance. When you request a random number of a certain type, the registry looks up and provides the correct generator to the API layer. The@register_randomdecorator allows new generators to be added to this registry automatically.The Generator Blueprint (base.py): This module defines the “abstract blueprint” that all random generators must follow. The
BaseRandomGeneratorclass establishes a common interface, requiring every generator to implement methods for creating both single fuzzy numbers (fuzznum()) and batches of them (fuzzarray()). It also provides theParameterizedRandomGeneratorsubclass, which includes helpful utilities for parameter management and sampling from standard distributions, simplifying the development of new generators.The Seed Management Layer (seed.py): This component is the guardian of reproducibility. It manages a global
numpy.random.Generatorinstance that all random generation tasks draw from. By setting a global seed withset_seed(), you fix the starting point of this random number generator, guaranteeing that every subsequent run of your code will produce identical results. It also supports creating independent random streams for advanced use cases like parallel computing.
A Call’s Lifecycle
To see how these components collaborate, let’s trace a typical call:
import axisfuzzy.random as fr
# User requests 100 q-Rung Orthopair Fuzzy Numbers
arr = fr.rand('qrofn', shape=(100,), q=3)
API Layer: The
rand()function inapi.pyreceives the request withmtype='qrofn'andshape=(100,).Registry Lookup: The API layer asks the
registry.pyto find the generator for'qrofn'. The registry returns an instance of theQROFNRandomGenerator.RNG Provision: The API layer retrieves the global random number generator (RNG) instance from
seed.pyto ensure the operation is part of the reproducible sequence.Generator Execution: Since a
shapeis specified, the API layer calls the high-performancefuzzarray()method on theQROFNRandomGeneratorinstance. This method uses the provided RNG to perform vectorized sampling, efficiently generates the membership and non-membership degrees according to theq-ROFNconstraints, and directly constructs aFuzzarrayobject for maximum performance.
The Generator Blueprint (base.py)
At the core of the axisfuzzy.random system’s extensibility is the “generator blueprint”
defined in base.py. This module establishes a clear and consistent contract that all
random generators must follow, ensuring that new fuzzy number types can be seamlessly
integrated into the high-level API. It provides two key abstract base classes:
BaseRandomGenerator and ParameterizedRandomGenerator.
BaseRandomGenerator: The Fundamental Contract
The BaseRandomGenerator is an abstract base class (ABC) that defines the
fundamental interface for any random generator. It cannot be instantiated directly.
Instead, its purpose is to enforce a “contract” that guarantees every generator,
regardless of the fuzzy number type it produces, will have a consistent structure
and set of capabilities.
Any class that inherits from BaseRandomGenerator must implement the following
four components:
mtypeAttribute A class attribute that declares the fuzzy number type (e.g.,'qrofn','ivfn') this generator is responsible for. The registry system uses this string to map a user’s request to the correct generator.class QROFNRandomGenerator(BaseRandomGenerator): mtype = 'qrofn'
get_default_parameters()Method This method must return a dictionary containing the default parameters for the generation process. These values are used when the user does not explicitly provide them, ensuring predictable behavior.def get_default_parameters(self) -> Dict[str, Any]: return { 'md_dist': 'uniform', 'md_low': 0.0, 'md_high': 1.0, 'nu_mode': 'orthopair' }
validate_parameters(**params)Method Before generation, the system calls this method to validate the user-provided and default parameters. It should raise aValueErrororTypeErrorif any parameter is invalid, preventing errors during the generation process. For example, it might check thatlowis not greater thanhigh.fuzznum(rng, **params)andfuzzarray(rng, shape, **params)Methods These are the core generation methods. The distinction is critical for performance:fuzznum(): Generates a singleFuzznuminstance. Its implementation is typically straightforward and easy to debug.fuzzarray(): Generates a batch of fuzzy numbers as aFuzzarrayof a givenshape. This method is designed for high performance and must be implemented using vectorized operations (e.g., with NumPy) to avoid slow Python loops.
This dual-method design allows for both simple, readable logic for single instances and highly optimized, scalable logic for large-scale simulations.
ParameterizedRandomGenerator: The Developer’s Toolkit
While BaseRandomGenerator defines the contract, implementing every generator from scratch would
involve repetitive boilerplate code for common tasks like merging parameters or sampling from statistical
distributions.
The ParameterizedRandomGenerator is a helper class that inherits from BaseRandomGenerator
and provides a powerful toolkit to solve these common problems. It is also an abstract class but comes
with pre-built utility methods. Developers are strongly encouraged to inherit from it whenever the
generation logic is based on sampling from standard statistical distributions.
Key utility methods provided by ParameterizedRandomGenerator:
_merge_parameters(**params)Automatically merges the user-provided parameters with the default parameters returned byget_default_parameters(). User parameters always take precedence. This simplifies parameter management significantly._validate_range(name, value, min_val, max_val)A convenient helper to check if a given parametervaluefalls within a specified[min_val, max_val]range, raising aValueErrorif it doesn’t._sample_from_distribution(...)This is the most powerful utility. It provides a unified interface to sample from various statistical distributions and automatically scales the output to a desired[low, high]range. Supported distributions include:'uniform': Uniform distribution.'beta': Beta distribution (requiresaandbshape parameters).'normal': Normal distribution (requireslocandscaleparameters). The output is clipped to the[low, high]range to prevent out-of-bounds values.
This method abstracts away the complexities of different NumPy sampling functions, allowing developers to focus on the generation logic itself.
Choosing the Right Base Class
Inherit from :class:`BaseRandomGenerator` directly if your generation logic is highly specialized, does not rely on standard statistical distributions, or requires complete control over every step of the process.
Inherit from :class:`ParameterizedRandomGenerator` (the recommended approach for most cases) if your generation logic involves sampling values from uniform, beta, or normal distributions to construct the fuzzy number’s components. This dramatically reduces development time and ensures consistency.
Reproducibility and State Management (seed.py)
In scientific computing and machine learning, reproducibility is a fundamental requirement.
Whether you are validating an algorithm, debugging code, or comparing experimental results,
the ability to control random processes is essential. The seed.py module is designed to
solve this problem by providing a robust and centralized system for managing the random state
across the entire axisfuzzy library.
The Core Concept: GlobalRandomState
At the heart of the seed.py module is the GlobalRandomState class, which operates
as a thread-safe singleton. This design ensures that:
Single Source of Truth: The entire library draws random numbers from a single, globally managed
numpy.random.Generatorinstance.Controllability: You can set a seed at any point to make subsequent random operations deterministic.
Thread Safety: A built-in
threading.Lockprotects the internal state, making it safe to use in multi-threaded applications.
This centralized approach prevents inconsistencies and makes it easy to track and control the source of randomness in your applications.
The GlobalRandomState Class
While users typically interact with the random state through the high-level functions described
below (like set_seed()), the GlobalRandomState class is the engine that powers the
entire system. It is implemented as a singleton, meaning a single instance of this class
manages the random state for the entire application lifecycle.
This class encapsulates a numpy.random.Generator instance and provides thread-safe methods
to manage it. Its primary responsibilities are:
Initialization: When first instantiated, it initializes a
Generatorwith entropy from the operating system, ensuring that initial random numbers are unpredictable until a seed is explicitly set.Seed Management: It handles the logic for setting and retrieving the seed, and re-creating the generator when the seed is changed via
set_seed().Generator Access: It provides methods to get the global generator (
get_generator()) or spawn new, independent generators (spawn_generator()).
The class is designed for internal use within the axisfuzzy library, but understanding its structure can
be helpful for advanced debugging or extension. The key methods of the class are wrapped and exposed by
the module-level functions for ease of use.
Core Functions for Random State Management
The module exposes a simple yet powerful API for interacting with the global random state.
set_seed()
This is the most crucial function for ensuring reproducibility. By calling set_seed()
with a specific integer, you initialize the global random number generator to a known state.
Every time you run your script with the same seed, you will get the exact same sequence of random numbers.
import axisfuzzy.random as fr
# Set the global seed to ensure reproducibility
fr.set_seed(42)
# All subsequent random generations are now deterministic
num1 = fr.rand('qrofn', q=2)
arr1 = fr.rand('ivfn', shape=(5,))
# Resetting to the same seed will produce the exact same results
fr.set_seed(42)
num2 = fr.rand('qrofn', q=2) # num2 is identical to num1
arr2 = fr.rand('ivfn', shape=(5,)) # arr2 is identical to arr1
assert num1 == num2
assert (arr1 == arr2).all()
get_rng()
For more advanced use cases where you need to perform custom random operations, get_rng()
provides direct access to the global numpy.random.Generator instance. This is useful when you
need to integrate with other NumPy-based libraries while maintaining a consistent random state.
Note
Using the generator returned by get_rng() will advance the global random state, affecting
all subsequent random operations within axisfuzzy.
import axisfuzzy.random as fr
import numpy as np
# Set the global seed to ensure reproducibility
fr.set_seed(123)
# Get the global random number generator
rng = fr.get_rng()
# Use it for custom random sampling
# This advances the global state
custom_noise = rng.normal(loc=0, scale=0.1, size=10)
# The next call to fr.rand() will use the advanced state
fuzzy_num = fr.rand('qrofn', q=2)
spawn_rng()
This powerful function allows you to create statistically independent random number generators derived from the global state. It is essential for parallel computing scenarios where you need to ensure that multiple processes or threads are generating random numbers without interfering with each other. Each spawned generator manages its own independent state.
import axisfuzzy.random as fr
from concurrent.futures import ThreadPoolExecutor
fr.set_seed(456)
def worker_task(worker_id):
# Each worker gets its own independent random stream
worker_rng = fr.spawn_rng()
# Generate data using the independent generator
# This does NOT affect the global state or other workers
data = worker_rng.uniform(0, 1, size=5)
return f"Worker {worker_id} data: {data.round(2)}"
# Execute tasks in parallel
with ThreadPoolExecutor(max_workers=2) as executor:
for result in executor.map(worker_task, range(2)):
print(result)
# The global state remains unaffected by the worker tasks
global_num = fr.rand('qrofn', q=2)
print(f"Global random number after parallel tasks: {global_num}")
get_seed()
This utility function allows you to retrieve the seed that was last used to initialize the global random state. This is useful for logging, debugging, or storing the configuration of an experiment for later replication.
import axisfuzzy.random as fr
fr.set_seed(789)
current_seed = fr.get_seed()
print(f"Experiment started with seed: {current_seed}")
# ... run experiment ...
results = fr.rand('qrofn', shape=(10,), q=2)
print(f"Experiment finished. Seed was: {fr.get_seed()}")
The Discovery Mechanism (registry.py)
The axisfuzzy.random system is designed to be extensible, allowing developers to add support for
new fuzzy number types without modifying the core library. This “plug-in” architecture is powered by
the discovery and registration mechanism in registry.py. It acts as a central directory, or
“phone book,” that maps each fuzzy number type (mtype) to its corresponding random generator.
This system ensures that when you call fr.rand('my_new_type'), the library automatically knows
where to find and how to use the generator for 'my_new_type'.
The Core: RandomGeneratorRegistry
The RandomGeneratorRegistry is a thread-safe singleton class that serves as the heart of the
discovery mechanism. It maintains an internal dictionary mapping mtype strings to generator instances.
Its key responsibilities are:
Centralized Management: Provides a single, global source of truth for all available random generators.
Automatic Discovery: Works with the
@register_randomdecorator to automatically find and register new generators when they are imported.Dynamic Dispatch: Enables the high-level API to look up and retrieve the correct generator instance at runtime based on the user’s request.
Seamless Registration with @register_random
The easiest and recommended way to add a new generator to the system is by using the @register_random
class decorator. When you apply this decorator to a generator class, it automatically performs the
following steps at module import time:
Reads the
mtypeclass attribute to identify which fuzzy type the generator handles.Creates an instance of the generator class.
Registers this instance with the global
RandomGeneratorRegistry.
This process is completely transparent and requires no manual intervention.
from axisfuzzy.random.base import ParameterizedRandomGenerator
from axisfuzzy.random.registry import register_random
# The decorator automatically registers this class upon import
@register_random
class MyNewTypeGenerator(ParameterizedRandomGenerator):
# The mtype that this generator is responsible for
mtype = "my_new_type"
# --- Implement the required methods ---
def get_default_parameters(self):
return {'param': 0.5}
def validate_parameters(self, **params):
pass
def fuzznum(self, rng, **params):
# ... logic to generate a single fuzzy number
pass
def fuzzarray(self, rng, shape, **params):
# ... logic to generate an array of fuzzy numbers
pass
Once this module is imported anywhere in your project, the 'my_new_type' generator becomes immediately
available through the high-level API.
Querying and Using Generators
The registry.py module also provides a set of user-friendly global functions to interact with the registry.
get_random_generator()
This function is the primary way to retrieve a generator instance for a specific mtype. The high-level
API uses it internally to dispatch requests.
from axisfuzzy.random.registry import get_random_generator
# Retrieve the generator for q-Rung Orthopair Fuzzy Numbers
qrofn_generator = get_random_generator('qrofn')
if qrofn_generator:
# You can now use the generator's methods directly
print(qrofn_generator.get_default_parameters())
list_registered_random() & is_registered_random() & get_registry_random()
To discover which generators are available or to check for the existence of a specific one, you can use these utility functions.
from axisfuzzy.random.registry import list_registered_random, is_registered_random, get_registry_random
# List all available random generators
available_generators = list_registered_random()
print(f"Available generators: {available_generators}")
# Expected output might include: ['qrofn', 'ivfn', 'qrohfn', ...]
# Check if a specific generator is registered
if is_registered_random('qrofn'):
print("The 'qrofn' generator is ready to use.")
else:
print("The 'qrofn' generator is not available.")
# Get the global registry instance for advanced operations
registry = get_registry_random()
print(f"Registry contains {len(registry._generators)} generators")
This registration and discovery system makes axisfuzzy highly modular and easy to extend,
encouraging community contributions and custom adaptations without compromising the stability of the core framework.
The High-Level API (api.py)
The api.py module serves as the primary user-facing interface for all random generation tasks
in axisfuzzy. It abstracts the underlying complexity of seed management, generator registration,
and type-specific logic into a set of powerful yet intuitive functions. This is the recommended entry
point for most users.
The architecture places this API at the top, providing a unified gateway to the entire random generation subsystem.
Key Functions:
rand: The main factory function for creating random
FuzznumandFuzzarrayinstances.choice: A function for random sampling from an existing
Fuzzarray, similar to NumPy’s equivalent.
The Primary Factory: rand()
The rand() function is the cornerstone of the high-level API. It is a versatile factory that can
generate both single fuzzy numbers and large, multi-dimensional fuzzy arrays with high performance.
rand(
mtype: Optional[str] = None,
q: int = 1,
shape: Optional[Union[int, Tuple[int, ...]]] = None,
rng: Optional[np.random.Generator] = None,
**params
) -> Union[Fuzznum, Fuzzarray]:
Core Parameters:
mtype: The type of fuzzy number to generate (e.g.,'qrofn','ivfn'). If omitted, the library uses the default mtype from the global configuration.q: The q-rung parameter, a structural property for types like q-Rung Orthopair Fuzzy Numbers.shape: Defines the output dimensions. IfNone(default), it returns a singleFuzznum. If anintortuple, it returns aFuzzarrayof the specified shape.seed&rng: Control reproducibility. See the note on randomness control below.**params: Keyword arguments passed directly to the type-specific generator. These control the statistical properties of the generated numbers, such as the distribution (md_dist) or range (md_low,md_high).
Randomness Control Priority
The API provides a flexible, two-tiered system for managing random state:
`rng` (Highest Priority): Pass an existing
numpy.random.Generatorinstance for full control. All randomness will be drawn from this generator.Global State (Lowest Priority): If rng is not provided, the function uses the library’s global generator, which is managed by
fr.set_seed().
Reproducible Sampling:
# Reproducible sampling with seed
fr.set_seed(123)
sample1 = fr.choice(population, size=5)
fr.set_seed(123)
sample2 = fr.choice(population, size=5) # Identical to sample1
Examples
Basic Generation:
import axisfuzzy.random as fr
# Generate a single q-Rung Orthopair Fuzzy Number (q=2)
num = fr.rand('qrofn', q=2)
# Generate a 1D array of 50 Interval-Valued Fuzzy Numbers
arr = fr.rand('ivfn', shape=50)
# Generate a 10x20 2D array
arr_2d = fr.rand('qrofn', q=3, shape=(10, 20))
Advanced Parameter Control:
This example generates 1,000 fuzzy numbers where the membership degree follows a Beta distribution.
arr_beta = fr.rand(
'qrofn',
q=4,
shape=(1000,),
md_dist='beta', # Membership degree from Beta distribution
md_low=0.2, # Clipped to a minimum of 0.2
md_high=0.8, # Clipped to a maximum of 0.8
a=2.0, # Beta distribution shape parameter `a`
b=5.0, # Beta distribution shape parameter `b`
nu_mode='orthopair' # Non-membership calculated to satisfy constraint
)
Random Sampling with choice()
The choice() function allows you to draw random samples from an existing 1-dimensional Fuzzarray.
It supports sampling with or without replacement and allows for weighted probabilities, mirroring the
functionality of numpy.random.choice.
choice(
obj: Fuzzarray,
size: Optional[Union[int, Tuple[int, ...]]] = None,
replace: bool = True,
p: Optional[Sequence[float]] = None,
rng: Optional[np.random.Generator] = None
) -> Union[Any, Fuzzarray]:
Core Parameters:
obj: The 1DFuzzarrayto sample from.size: The desired shape of the output. IfNone, returns a singleFuzznum.replace: IfTrue(default), samples are drawn with replacement. IfFalse, they are drawn without replacement.p: An array-like sequence of probabilities associated with each element in obj.
Examples
Basic Sampling:
import axisfuzzy.random as fr
# 1. Create a source array
source_arr = fr.rand('qrofn', q=2, shape=(1000,))
# 2. Sample a single element
single_sample = fr.choice(source_arr)
# 3. Sample 50 elements with replacement
samples_with_replacement = fr.choice(source_arr, size=50)
# 4. Sample 50 unique elements without replacement
samples_without_replacement = fr.choice(source_arr, size=50, replace=False)
Weighted Sampling:
This is useful when certain fuzzy numbers in the source array are more important than others.
import numpy as np
# Assume we have a source array of 100 elements
source_arr = fr.rand('qrofn', q=2, shape=(100,))
# Create random weights and normalize them to sum to 1
weights = np.random.rand(100)
weights /= weights.sum()
# Draw a weighted sample of 20 elements
weighted_sample = fr.choice(source_arr, size=20, p=weights)
Utility Functions
The API also includes helper functions like uniform(), normal(), and beta(), which are convenient
wrappers around NumPy’s random generation functions. They are integrated with AxisFuzzy’s seed management
system, making them useful for generating auxiliary numerical data for your fuzzy logic models.
Additionally, the API provides several utility functions for advanced random generation scenarios:
``get_rng()``: Returns the global random number generator instance
``set_seed()``: Sets the global random seed for reproducible generation
``spawn_rng()``: Creates an independent random generator for parallel operations
``get_seed()``: Retrieves the current global seed value
Note
Currently, these utility functions return standard floating-point numbers (float or numpy.ndarray).
In the future, they may be enhanced to generate fuzzy numbers that follow these distributions directly.
Extending the System
The axisfuzzy.random framework is engineered for extensibility. Its plug-in architecture allows you to
add custom random generators for new or existing fuzzy number types without modifying the core library.
This section provides a comprehensive, end-to-end tutorial on how to create, register, and use a new
random generator.
As a practical case study, we will develop an alternative random generator for the existing qrofn
(q-Rung Orthopair Fuzzy Number) type. This new generator will have a different default behavior,
demonstrating how you can tailor generation logic to specific experimental needs.
The Three-Step Process
Adding a new generator follows a consistent, three-step pattern that leverages the concepts from
base.py and registry.py:
Implement the Generator Class: Create a Python class that inherits from a suitable base class, typically
ParameterizedRandomGenerator. This class will contain all the logic for generating your fuzzy numbers.Register the Generator: Apply the
@register_randomdecorator to your class. This automatically makes the generator available to the high-level API (e.g.,fr.rand()) as soon as the module containing it is imported.Ensure Module Import: Make sure the Python module containing your new generator is imported at an appropriate place in your project, so the registration process is triggered.
End-to-End Example: An Alternative Generator for qrofn
Let’s imagine we need a qrofn generator that, by default, produces numbers with membership and
non-membership degrees clustered around the middle of the range, rather than uniformly distributed.
We can achieve this by using a Beta distribution as the default.
We will name our new generator 'qrofn_beta' to distinguish it from the standard 'qrofn' generator.
Step 1 & 2: Implement and Register the Generator
We create a new Python file (e.g., in your project’s utility module) and add the following code. This single block of code defines the class and registers it.
import numpy as np
from typing import Any, Dict, Tuple
from axisfuzzy.core import Fuzznum, Fuzzarray
from axisfuzzy.random.base import ParameterizedRandomGenerator
from axisfuzzy.random.registry import register_random
from axisfuzzy.fuzztype.qrofs.backend import QROFNBackend
@register_random # The decorator that makes it all work
class QROFNBetaRandomGenerator(ParameterizedRandomGenerator):
"""
An alternative random generator for q-Rung Orthopair Fuzzy Numbers
that defaults to a Beta distribution for more centralized values.
"""
# The mtype key that users will use to call this generator
mtype = "qrofn_beta"
def get_default_parameters(self) -> Dict[str, Any]:
"""
Define default parameters. Here, we set the distribution to 'beta'.
"""
return {
'md_dist': 'beta', # Default to Beta distribution
'md_low': 0.0,
'md_high': 1.0,
'nu_mode': 'orthopair', # Generate non-membership under the constraint
'nu_dist': 'uniform',
'nu_low': 0.0,
'nu_high': 1.0,
# Default shape parameters for the Beta distribution
'a': 2.0,
'b': 2.0,
# Default parameters for other distributions (e.g., normal)
'loc': 0.5,
'scale': 0.15
}
def validate_parameters(self, **params) -> None:
"""
Validate the structural and distributional parameters.
"""
# Validate the q-rung parameter
if 'q' not in params or not isinstance(params['q'], int) or params['q'] <= 0:
raise ValueError(f"q must be a positive integer, got {params.get('q')}")
# Use the built-in helper to validate ranges
self._validate_range('md_low', params.get('md_low', 0.0), 0.0, 1.0)
self._validate_range('md_high', params.get('md_high', 1.0), 0.0, 1.0)
if params.get('md_low', 0.0) > params.get('md_high', 1.0):
raise ValueError("md_low cannot be greater than md_high")
def fuzznum(self, rng: np.random.Generator, **params) -> 'Fuzznum':
"""
Generate a single 'qrofn_beta' fuzzy number.
"""
# 1. Merge user-provided parameters with our defaults
p = self._merge_parameters(**params)
self.validate_parameters(**p)
# 2. Generate membership degree (md) using our sampling utility
md = self._sample_from_distribution(
rng, dist=p['md_dist'], low=p['md_low'], high=p['md_high'],
a=p['a'], b=p['b'], loc=p['loc'], scale=p['scale']
)
# 3. Generate non-membership degree (nmd) based on the q-ROFN constraint
max_nmd = (1 - md**p['q'])**(1 / p['q'])
nmd = rng.uniform(0, max_nmd) # Sample uniformly within the valid range
# 4. Create the Fuzznum instance
return Fuzznum(mtype=self.mtype, q=p['q']).create(md=md, nmd=nmd)
def fuzzarray(self, rng: np.random.Generator, shape: Tuple[int, ...], **params) -> 'Fuzzarray':
"""
High-performance batch generation of 'qrofn_beta' fuzzy arrays.
"""
# 1. Merge and validate parameters
p = self._merge_parameters(**params)
self.validate_parameters(**p)
size = int(np.prod(shape))
# 2. Vectorized generation of membership degrees
mds = self._sample_from_distribution(
rng, size=size, dist=p['md_dist'], low=p['md_low'], high=p['md_high'],
a=p['a'], b=p['b'], loc=p['loc'], scale=p['scale']
)
# 3. Vectorized generation of non-membership degrees
max_nmds = (1 - mds**p['q'])**(1 / p['q'])
# Efficiently sample within the constraint for the whole array
nmds = rng.uniform(0, 1, size=size) * max_nmds
# 4. Directly construct the backend for maximum performance
backend = QROFNBackend.from_arrays(
mds=mds.reshape(shape),
nmds=nmds.reshape(shape),
q=p['q']
)
return Fuzzarray(backend=backend)
Step 3: Use the New Generator
Now, as long as the Python module containing AlternativeQROFNGenerator is imported, you can use it
directly with fr.rand() by specifying mtype='qrofn_beta'.
import axisfuzzy.random as fr
# Make sure the module with AlternativeQROFNGenerator is imported, e.g.:
# from .my_custom_generators import QROFNBetaRandomGenerator
# Generate a 100-element array using our new generator
# It will use a Beta(2,2) distribution by default
beta_arr = fr.rand('qrofn_beta', q=3, shape=100)
# We can still override the defaults, just like with any other generator
uniform_arr = fr.rand('qrofn_beta', q=3, shape=100, md_dist='uniform')
# Check which generators are available
print(fr.list_registered())
# Expected output will now include: ['qrofn', 'ivfn', ..., 'qrofn_beta']
Key Takeaways from the Example
Inherit from `ParameterizedRandomGenerator`: This provides powerful helpers like
_merge_parametersand_sample_from_distribution, saving you from writing boilerplate code.Set the `mtype` Attribute: This unique string is the key that links your generator to the
fr.rand()function.Implement the Four Core Methods:
get_default_parameters,validate_parameters,fuzznum, andfuzzarray.Vectorize `fuzzarray` for Performance: The most critical performance aspect is to use vectorized NumPy operations for batch generation and construct the backend directly, avoiding slow Python loops.
The `@register_random` Decorator Does the Magic: This simple decorator handles all the complexity of making your generator discoverable by the rest of the library.
Conclusion
This guide has provided a comprehensive tour of the axisfuzzy.random system, a powerful and
flexible framework for generating random fuzzy numbers. We have explored its modular architecture,
designed for extensibility and performance, and delved into the core components that ensure
reproducibility and ease of use.
You have learned how to:
Leverage the high-level API: Use functions like
fr.rand()andfr.choice()to effortlessly generate various types of random fuzzy numbers.Ensure Reproducibility: Control the random state with
fr.set_seed()to guarantee that your experiments are deterministic and verifiable.Understand the Architecture: Appreciate how the API, Registry, Generator Blueprint, and Seed Management layers work together to provide a seamless experience.
Extend the System: Create and register your own custom random generators by inheriting from
ParameterizedRandomGeneratorand using the@register_randomdecorator.
By mastering these concepts, you are now equipped to harness the full potential of axisfuzzy for a
wide range of applications, from sophisticated Monte Carlo simulations to robust algorithm testing.
The system’s design not only meets current needs but also provides a solid foundation for future extensions,
ensuring that axisfuzzy remains at the forefront of fuzzy logic research and development.