Source code for axisfuzzy.random.seed

#  Copyright (c) yibocat 2025 All Rights Reserved
#  Python: 3.12.7
#  Date: 2025/8/18 18:23
#  Author: yibow
#  Email: yibocat@yeah.net
#  Software: AxisFuzzy

"""
Random seed management for reproducible fuzzy number generation.

This module provides a centralized random state management system that ensures
reproducible random generation across the entire AxisFuzzy library while
maintaining thread safety and high performance. It serves as the foundation
for all random fuzzy number generation operations.

The module implements a global singleton pattern for random state management,
ensuring that all random generators throughout the library share a consistent
and controllable random state. This is essential for scientific reproducibility
and deterministic behavior in fuzzy computations.

Architecture
------------
The random state management follows these key principles:

- **Global Consistency**: Single source of truth for random state across the library
- **Thread Safety**: All operations are protected by locks for concurrent access
- **Reproducibility**: Deterministic behavior through proper seed management
- **Independence**: Ability to spawn independent generators for parallel operations
- **Performance**: Efficient NumPy generator usage with minimal overhead

The system is built around NumPy's modern random number generation framework,
using `numpy.random.Generator` instances for high-performance random sampling.

Classes
-------
GlobalRandomState
    Thread-safe manager for global random state and generator instances.

Functions
---------
set_seed : Set global random seed for reproducible generation
get_rng : Get the global random number generator instance
spawn_rng : Create independent generator for parallel operations
get_seed : Retrieve the current global seed value

See Also
--------
axisfuzzy.random.api : High-level random generation API
axisfuzzy.random.base : Abstract base classes for random generators
numpy.random : NumPy's random number generation framework

Examples
--------
Basic seed management:

.. code-block:: python

    import axisfuzzy.random as fr

    # Set global seed for reproducibility
    fr.set_seed(42)

    # Generate random fuzzy numbers - results will be reproducible
    num1 = fr.rand('qrofn', q=2)
    arr1 = fr.rand('qrofn', shape=(100,), q=3)

    # Reset to same seed to get identical results
    fr.set_seed(42)
    num2 = fr.rand('qrofn', q=2)  # Identical to num1
    arr2 = fr.rand('qrofn', shape=(100,), q=3)  # Identical to arr1

Advanced usage with independent generators:

.. code-block:: python

    import axisfuzzy.random as fr
    import numpy as np

    # Set global seed
    fr.set_seed(123)

    # Get the global generator for custom use
    rng = fr.get_rng()
    custom_samples = rng.uniform(0, 1, size=10)

    # Spawn independent generator for parallel work
    independent_rng = fr.spawn_rng()
    parallel_samples = independent_rng.normal(0, 1, size=100)

Notes
-----
The module uses NumPy's modern random number generation system introduced
in NumPy 1.17+. This provides better performance, statistical properties,
and parallel support compared to the legacy `numpy.random` interface.

All generators are instances of `numpy.random.Generator`, which is the
recommended interface for new code. The module ensures thread safety
through appropriate locking mechanisms without significant performance
penalties for typical usage patterns.
"""

import threading
from typing import Optional, Union

import numpy as np


[docs] class GlobalRandomState: """ Thread-safe global random state manager for AxisFuzzy. This class manages a single numpy.random.Generator instance that serves as the primary source of randomness throughout the AxisFuzzy library. It provides thread-safe access to random number generation capabilities while ensuring reproducible behavior through proper seed management. The class implements a singleton-like pattern where a single global instance manages the random state for the entire library, ensuring consistency across all random operations. Attributes ---------- _lock : threading.Lock Thread synchronization lock for safe concurrent access. _rng : numpy.random.Generator The primary random number generator instance. _seed : int, numpy.random.SeedSequence, numpy.random.BitGenerator, or None The current seed used to initialize the generator. Notes ----- This class uses NumPy's modern random number generation framework based on `numpy.random.Generator`. This provides better performance and statistical properties compared to the legacy `numpy.random` interface. All methods are thread-safe and can be called concurrently from multiple threads without data races or inconsistent state. The generator can be seeded with various types: - `int`: Simple integer seed - `numpy.random.SeedSequence`: Advanced seed sequence for better entropy - `numpy.random.BitGenerator`: Pre-configured bit generator Examples -------- Basic usage (typically done through module functions): .. code-block:: python # Create and configure global state state = GlobalRandomState() state.set_seed(42) # Get generator for random operations rng = state.get_generator() values = rng.uniform(0, 1, size=100) # Spawn independent generator independent_rng = state.spawn_generator() parallel_values = independent_rng.normal(0, 1, size=50) """ def __init__(self): """ Initialize the global random state manager. Creates a new random state manager with default initialization. The generator is initialized with system entropy for unpredictable behavior until a specific seed is set. """ self._lock = threading.Lock() self._rng = np.random.default_rng() self._seed = None
[docs] def set_seed(self, seed: Optional[Union[int, np.random.SeedSequence, np.random.BitGenerator]] = None): """ Set the global random seed for reproducible generation. Configures the internal random number generator with a specific seed, enabling reproducible random number generation across the entire AxisFuzzy library. This is essential for scientific reproducibility and debugging. Parameters ---------- seed : int, numpy.random.SeedSequence, numpy.random.BitGenerator, optional The seed to use for random number generation. Different types provide different levels of control: - `int`: Simple integer seed (0 to 2^32-1) - `numpy.random.SeedSequence`: Advanced seed with entropy mixing - `numpy.random.BitGenerator`: Pre-configured bit generator - `None`: Use system entropy for non-reproducible behavior Notes ----- Setting the seed affects all subsequent random operations in the library, including fuzzy number generation, random sampling, and stochastic operations. Once set, the sequence of random numbers becomes deterministic and reproducible. For maximum reproducibility across different platforms and NumPy versions, consider using `numpy.random.SeedSequence` instead of raw integers. Examples -------- Different seeding approaches: .. code-block:: python # Simple integer seed state.set_seed(42) # Using SeedSequence for better entropy seed_seq = np.random.SeedSequence(12345) state.set_seed(seed_seq) # Using specific bit generator bit_gen = np.random.PCG64(67890) state.set_seed(bit_gen) # Reset to non-deterministic behavior state.set_seed(None) """ with self._lock: self._seed = seed self._rng = np.random.default_rng(seed)
[docs] def get_generator(self) -> np.random.Generator: """ Get the global random number generator instance. Returns the current Generator instance used for all random operations in the library. This generator can be used directly for custom random sampling operations that need to be consistent with the global random state. Returns ------- numpy.random.Generator The current global random number generator instance. Notes ----- The returned generator is the actual instance used internally, not a copy. This means that using it directly will advance the internal random state and affect subsequent library operations. For operations that should not interfere with the global state, consider using :meth:`spawn_generator` instead. Thread safety is maintained through internal locking, so the generator can be safely accessed from multiple threads. Examples -------- Direct generator usage: .. code-block:: python # Get global generator rng = state.get_generator() # Use for custom sampling custom_values = rng.uniform(0, 1, size=1000) beta_values = rng.beta(2, 5, size=500) # This advances the global state, affecting subsequent # library operations fuzz_num = fr.rand('qrofn') # Uses advanced state """ with self._lock: return self._rng
[docs] def get_seed(self) -> Union[int, np.random.SeedSequence, np.random.BitGenerator]: """ Get the current global random seed. Returns the seed value that was used to initialize the current random number generator. This can be useful for debugging, logging, or re-creating the current random state. Returns ------- int, numpy.random.SeedSequence, numpy.random.BitGenerator, or None The seed value used to initialize the current generator. Returns None if the generator was initialized with system entropy. Notes ----- The returned value is the original seed passed to :meth:`set_seed`, not the current internal state of the generator. The generator's internal state changes with each random number drawn. To fully reproduce a sequence, you need both the original seed and knowledge of how many random numbers have been drawn since initialization. Examples -------- .. code-block:: python # Set a seed and retrieve it state.set_seed(42) current_seed = state.get_seed() print(f"Current seed: {current_seed}") # Output: Current seed: 42 # Use for logging or debugging if current_seed is not None: print(f"Random state is deterministic with seed {current_seed}") else: print("Random state uses system entropy") """ with self._lock: return self._seed
[docs] def spawn_generator(self) -> np.random.Generator: """ Create an independent random number generator. Spawns a new Generator instance that is statistically independent of the global generator. This is useful for parallel operations, isolated random processes, or when you need randomness that doesn't interfere with the global random state. Returns ------- numpy.random.Generator A new, statistically independent random number generator. Notes ----- The spawned generator is completely independent of the parent: - Drawing numbers from it doesn't affect the global state - It has its own internal state and sequence - It's suitable for parallel processing without interference Spawning uses NumPy's recommended approach for creating independent generators, ensuring proper statistical independence and avoiding correlation issues that can occur with naive approaches. This method is particularly useful for: - Parallel fuzzy number generation - Isolated experiments within larger computations - Monte Carlo simulations with independent streams Examples -------- Independent random generation: .. code-block:: python # Set global state state.set_seed(42) # Spawn independent generator independent_rng = state.spawn_generator() # Use independent generator independent_values = independent_rng.uniform(0, 1, size=100) # Global state is unaffected global_values = state.get_generator().uniform(0, 1, size=100) # These sequences are statistically independent print("Independent and global sequences are uncorrelated") Parallel processing example: .. code-block:: python import concurrent.futures def generate_random_array(seed_offset): # Each worker gets independent generator worker_rng = state.spawn_generator() return worker_rng.normal(0, 1, size=1000) # Generate multiple independent arrays in parallel with concurrent.futures.ThreadPoolExecutor() as executor: futures = [executor.submit(generate_random_array, i) for i in range(10)] results = [f.result() for f in futures] """ with self._lock: # Use the generator's spawn method, which is the recommended way # to create statistically independent child generators. return self._rng.spawn(1)[0]
# Global instance _global_random_state = GlobalRandomState() # Public interface functions
[docs] def set_seed(seed: Optional[Union[int, np.random.SeedSequence, np.random.BitGenerator]] = None): """ Set the global random seed for AxisFuzzy. This function provides the primary interface for controlling randomness throughout the AxisFuzzy library. Setting a seed ensures that all random operations (fuzzy number generation, random sampling, etc.) produce reproducible results. Parameters ---------- seed : int, numpy.random.SeedSequence, numpy.random.BitGenerator, optional The seed to use for random number generation: - `int`: Simple integer seed (recommended for basic use) - `numpy.random.SeedSequence`: Advanced seed sequence for better control - `numpy.random.BitGenerator`: Pre-configured bit generator - `None`: Use system entropy for non-reproducible behavior See Also -------- get_seed : Retrieve the current global seed get_rng : Get the global random number generator numpy.random.default_rng : NumPy's default generator function Examples -------- Basic reproducible generation: .. code-block:: python import axisfuzzy.random as fr # Set seed for reproducibility fr.set_seed(42) # Generate fuzzy numbers - results are reproducible num1 = fr.rand('qrofn', q=2) arr1 = fr.rand('qrofn', shape=(10,), q=3) # Reset seed to get identical results fr.set_seed(42) num2 = fr.rand('qrofn', q=2) # Identical to num1 arr2 = fr.rand('qrofn', shape=(10,), q=3) # Identical to arr1 print(num1 == num2) # True print((arr1 == arr2).all()) # True Scientific workflow with reproducibility: .. code-block:: python import axisfuzzy.random as fr # Set seed at start of experiment EXPERIMENT_SEED = 12345 fr.set_seed(EXPERIMENT_SEED) # Generate datasets training_data = fr.rand('qrofn', shape=(1000, 10), q=2) test_data = fr.rand('qrofn', shape=(200, 10), q=2) # Results are now reproducible across runs print(f"Experiment run with seed {EXPERIMENT_SEED}") Different seed types: .. code-block:: python import numpy as np import axisfuzzy.random as fr # Simple integer seed fr.set_seed(123) # Advanced seed sequence seed_seq = np.random.SeedSequence(123, spawn_key=(1, 2, 3)) fr.set_seed(seed_seq) # Custom bit generator bit_gen = np.random.PCG64(456) fr.set_seed(bit_gen) """ _global_random_state.set_seed(seed)
[docs] def get_rng() -> np.random.Generator: """ Get the global random number generator for AxisFuzzy. Returns the Generator instance used throughout the library for all random operations. This can be used for custom random sampling that needs to be consistent with the library's random state. Returns ------- numpy.random.Generator The global random number generator instance. Notes ----- The returned generator is the actual instance used internally by the library. Using it directly will advance the global random state and affect subsequent library operations. For independent random generation that doesn't interfere with the global state, use :func:`spawn_rng` instead. See Also -------- spawn_rng : Create independent random generator set_seed : Set the global random seed numpy.random.Generator : NumPy generator documentation Examples -------- Custom random sampling: .. code-block:: python import axisfuzzy.random as fr # Set seed for reproducibility fr.set_seed(42) # Get global generator rng = fr.get_rng() # Use for custom sampling consistent with global state membership_values = rng.beta(2, 5, size=100) noise = rng.normal(0, 0.1, size=100) # Subsequent library operations use advanced state fuzz_array = fr.rand('qrofn', shape=(50,), q=2) Integration with NumPy operations: .. code-block:: python import axisfuzzy.random as fr import numpy as np # Get generator for NumPy compatibility rng = fr.get_rng() # Use NumPy methods with consistent state indices = rng.choice(1000, size=100, replace=False) weights = rng.dirichlet([1, 1, 1, 1]) # Mix with AxisFuzzy operations fuzzy_samples = fr.rand('qrofn', shape=(len(indices),), q=3) """ return _global_random_state.get_generator()
[docs] def spawn_rng() -> np.random.Generator: """ Create an independent random number generator. Spawns a new Generator instance that is statistically independent of the global generator. This is ideal for parallel operations, isolated computations, or when you need random numbers without affecting the global random state. Returns ------- numpy.random.Generator A new, statistically independent random number generator. Notes ----- The spawned generator is completely independent: - Using it doesn't affect the global random state - It maintains its own internal state and sequence - It's safe for parallel processing without interference This function uses NumPy's recommended spawning mechanism to ensure proper statistical independence and avoid correlation issues. See Also -------- get_rng : Get the global random generator set_seed : Set the global random seed numpy.random.Generator.spawn : NumPy's generator spawning method Examples -------- Independent random operations: .. code-block:: python import axisfuzzy.random as fr # Set global seed fr.set_seed(42) # Create independent generator independent_rng = fr.spawn_rng() # Use independent generator without affecting global state independent_data = independent_rng.uniform(0, 1, size=1000) # Global state remains unaffected global_fuzz = fr.rand('qrofn', q=2) # Predictable based on seed 42 Parallel processing with independent streams: .. code-block:: python import axisfuzzy.random as fr from concurrent.futures import ThreadPoolExecutor def worker_function(worker_id): # Each worker gets its own independent generator worker_rng = fr.spawn_rng() # Generate data independently data = worker_rng.normal(0, 1, size=100) fuzzy_data = [] # Use worker's generator for fuzzy generation for _ in range(10): # This would typically use a custom generator # For demonstration, we'll use direct values md = worker_rng.uniform(0, 1) nmd = worker_rng.uniform(0, 1-md**2)**(1/2) # q=2 constraint fuzzy_data.append((md, nmd)) return worker_id, data, fuzzy_data # Run parallel workers with independent randomness fr.set_seed(123) # Global seed for reproducibility with ThreadPoolExecutor(max_workers=4) as executor: results = list(executor.map(worker_function, range(4))) # Each worker produced independent results for worker_id, data, fuzzy_data in results: print(f"Worker {worker_id}: generated {len(data)} samples") Monte Carlo simulation with independent streams: .. code-block:: python import axisfuzzy.random as fr def monte_carlo_trial(): # Independent generator for each trial trial_rng = fr.spawn_rng() # Generate random fuzzy scenario scenario_data = trial_rng.uniform(0, 1, size=100) # Simulate some process result = scenario_data.mean() return result # Run multiple independent trials fr.set_seed(456) trials = [monte_carlo_trial() for _ in range(1000)] # Analyze results import numpy as np print(f"Monte Carlo estimate: {np.mean(trials):.4f} ± {np.std(trials):.4f}") """ return _global_random_state.spawn_generator()
[docs] def get_seed() -> Optional[Union[int, np.random.SeedSequence, np.random.BitGenerator]]: """ Get the current global random seed. Returns the seed value that was used to initialize the global random number generator. This is useful for debugging, logging, experiment tracking, or reproducing specific random states. Returns ------- int, numpy.random.SeedSequence, numpy.random.BitGenerator, or None The seed value used to initialize the current generator. Returns None if the generator was initialized with system entropy. Notes ----- This function returns the original seed passed to :func:`set_seed`, not the current internal state of the generator. The generator's internal state evolves with each random number generated. To fully reproduce a random sequence, you need both the original seed and knowledge of the generation history (how many random numbers have been drawn). See Also -------- set_seed : Set the global random seed get_rng : Get the global random generator Examples -------- Basic seed retrieval: .. code-block:: python import axisfuzzy.random as fr # Set and retrieve seed fr.set_seed(42) current_seed = fr.get_seed() print(f"Current seed: {current_seed}") # Output: Current seed: 42 Experiment logging: .. code-block:: python import axisfuzzy.random as fr # Set up experiment experiment_seed = 12345 fr.set_seed(experiment_seed) # Log experiment parameters print(f"Starting experiment with seed: {fr.get_seed()}") # Run experiment results = fr.rand('qrofn', shape=(100,), q=2) # Log for reproducibility print(f"Experiment completed with seed {fr.get_seed()}") print(f"To reproduce: fr.set_seed({fr.get_seed()})") Conditional behavior based on seed: .. code-block:: python import axisfuzzy.random as fr # Check if random behavior is deterministic if fr.get_seed() is not None: print("Random generation is reproducible") print(f"Seed: {fr.get_seed()}") else: print("Random generation uses system entropy") print("Results will not be reproducible") # Generate data data = fr.rand('qrofn', shape=(10,), q=3) """ return _global_random_state.get_seed()