Source code for axisfuzzy.membership.factory

#  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

"""
Factory functions for creating membership function instances in AxisFuzzy.

This module provides a centralized factory system for creating membership function
instances by name, supporting both full class names and convenient aliases. It
automatically discovers available membership function classes and provides a
flexible parameter-passing mechanism that separates membership function parameters
from other system parameters.

The factory system enables:
- Dynamic membership function creation from string names
- Automatic class discovery and registration
- Flexible parameter handling with automatic separation
- User-friendly aliases for common membership functions
- Integration with the broader AxisFuzzy fuzzification system

Architecture
------------
The factory system consists of three key components:

1. **Class Discovery**: Automatically scans :mod:`axisfuzzy.membership.function`
   to build a registry of available membership function classes.

2. **Alias System**: Provides user-friendly short names and alternative spellings
   for membership functions (e.g., 'trimf' for TriangularMF).

3. **Parameter Separation**: Intelligently separates membership function parameters
   from other system parameters during instance creation.

Core Functions
--------------
The factory provides two main functions:

- ``get_mf_class(name)``: Resolves a name/alias to the corresponding
  :class:`MembershipFunction` subclass for direct instantiation.

- ``create_mf(name, **kwargs)``: Creates a membership function instance
  with automatic parameter handling and returns unused parameters.

Automatic Class Discovery
-------------------------
The factory automatically discovers all :class:`MembershipFunction` subclasses
defined in :mod:`axisfuzzy.membership.function` using Python's introspection
capabilities. This means:

- New membership function classes are automatically available
- No manual registration is required for standard functions
- The system stays synchronized with available implementations

Alias System
------------
The factory supports multiple naming conventions:

**Standard Aliases** (manually maintained):
    - 'trimf' → :class:`TriangularMF`
    - 'trapmf' → :class:`TrapezoidalMF`
    - 'gaussmf' → :class:`GaussianMF`
    - 'smf' → :class:`SMF`
    - 'zmf' → :class:`ZMF`
    - 'gbellmf' → :class:`GeneralizedBellMF`
    - 'pimf' → :class:`PiMF`
    - 'gauss2mf' → :class:`DoubleGaussianMF`
    - 'sigmoid' → :class:`SigmoidMF`

**Automatic Aliases** (generated):
    - All class names in lowercase (e.g., 'triangularmf' → :class:`TriangularMF`)
    - Full class names (e.g., 'TriangularMF' → :class:`TriangularMF`)

Parameter Handling
------------------
The :func:`create_mf` function uses introspection to intelligently separate
parameters intended for the membership function constructor from other
system parameters:

1. **Constructor Analysis**: Inspects the target class's ``__init__`` method
   to identify required and optional parameters.

2. **Parameter Separation**: Splits input kwargs into membership function
   parameters and remaining system parameters.

3. **Clean Returns**: Returns both the created instance and unused parameters
   for further processing by calling systems.

This design allows the factory to be embedded in larger systems (like the
fuzzification system) where multiple components may share parameter dictionaries.

Error Handling
--------------
The factory provides clear error messages for common issues:

- **Unknown Functions**: Lists all available functions when an invalid name is provided
- **Parameter Errors**: Preserves and re-raises constructor validation errors
- **Import Errors**: Handles missing dependencies gracefully

Notes
-----
- All function names and aliases are case-insensitive for user convenience
- The factory system is thread-safe for read operations
- New membership functions added to the function module are automatically available
- Parameter validation is delegated to the individual membership function constructors

See Also
--------
axisfuzzy.membership.base : Base class for all membership functions
axisfuzzy.membership.function : Standard membership function implementations
axisfuzzy.fuzzify : Fuzzification system that uses this factory

Examples
--------
Basic factory usage:

.. code-block:: python

    from axisfuzzy.membership.factory import create_mf, get_mf_class

    # Create instances using aliases
    tri_mf, unused = create_mf('trimf', a=0, b=0.5, c=1)
    gauss_mf, unused = create_mf('gaussmf', sigma=1.0, c=0.5)

    # Create using full class names
    trap_mf, unused = create_mf('TrapezoidalMF', a=0, b=0.2, c=0.8, d=1)

    # Case-insensitive names
    sigmoid_mf, unused = create_mf('SIGMOID', k=2.0, c=0.5)

Parameter separation in system integration:

.. code-block:: python

    # Mixed parameters for fuzzification system
    all_params = {
        'a': 0, 'b': 0.5, 'c': 1,           # For membership function
        'mtype': 'qrofn', 'q': 2,           # For fuzzy system
        'method': 'direct'                   # For fuzzification method
    }

    # Factory separates parameters automatically
    mf, system_params = create_mf('trimf', **all_params)
    print(system_params)  # {'mtype': 'qrofn', 'q': 2, 'method': 'direct'}

Direct class access:

.. code-block:: python

    # Get class reference for advanced usage
    MFClass = get_mf_class('triangularmf')

    # Create multiple instances efficiently
    mf1 = MFClass(a=0, b=0.3, c=0.6)
    mf2 = MFClass(a=0.4, b=0.7, c=1.0)

Error handling:

.. code-block:: python

    try:
        mf, unused = create_mf('unknown_function', param=1)
    except ValueError as e:
        print(e)  # Lists all available functions

    try:
        mf, unused = create_mf('trimf', a=1, b=0.5, c=0)  # Invalid order
    except ValueError as e:
        print(e)  # TriangularMF validation error

Integration with other systems:

.. code-block:: python

    # Typical usage in fuzzification system
    def create_fuzzifier(mf_name, **params):
        # Create membership function with parameter separation
        mf, remaining_params = create_mf(mf_name, **params)

        # Use remaining parameters for other components
        mtype = remaining_params.get('mtype', 'qrofn')
        method = remaining_params.get('method', 'direct')

        # Build complete system
        return SomeFuzzificationSystem(mf, mtype, method)

References
----------
- Python ``inspect`` module documentation for introspection techniques
- Factory Method pattern in software design
- Plugin architecture patterns for extensible systems
"""

import inspect
from typing import Dict, Type, Any, Tuple

from .base import MembershipFunction
from . import function as mfs


# Automatic class discovery: Build mapping from class names to class objects
# This uses introspection to find all MembershipFunction subclasses in the function module
# e.g., {'TriangularMF': <class 'TriangularMF'>, 'GaussianMF': <class 'GaussianMF'>, ...}
_mf_class_map: Dict[str, Type[MembershipFunction]] = {
    name: obj for name, obj in inspect.getmembers(mfs, inspect.isclass)
    if issubclass(obj, MembershipFunction) and obj is not MembershipFunction
}

# Manual alias mapping: User-friendly names and alternative spellings
# These are carefully curated aliases that follow MATLAB Fuzzy Logic Toolbox conventions
# and provide convenient short forms for common membership functions
_mf_alias_map: Dict[str, Type[MembershipFunction]] = {
    'sigmoid': mfs.SigmoidMF,           # Standard sigmoid function
    'trimf': mfs.TriangularMF,          # MATLAB-style triangular
    'trapmf': mfs.TrapezoidalMF,        # MATLAB-style trapezoidal
    'gaussmf': mfs.GaussianMF,          # MATLAB-style Gaussian
    'smf': mfs.SMF,                     # S-shaped membership function
    'zmf': mfs.ZMF,                     # Z-shaped membership function
    'gbellmf': mfs.GeneralizedBellMF,   # MATLAB-style generalized bell
    'pimf': mfs.PiMF,                   # Pi-shaped membership function
    'gauss2mf': mfs.DoubleGaussianMF    # MATLAB-style double Gaussian
}

# Automatic alias generation: Add lowercase versions of all class names
# This allows users to use either exact class names or lowercase versions
# e.g., 'TriangularMF' or 'triangularmf' both work
_mf_alias_map.update({k.lower(): v for k, v in _mf_class_map.items()})


[docs] def get_mf_class(name: str) -> Type[MembershipFunction]: """ Resolve a membership function name to its corresponding class. This function provides a unified interface for obtaining membership function classes by name, supporting both full class names and convenient aliases. Name matching is case-insensitive for user convenience. Parameters ---------- name : str Name or alias of the membership function. Can be: - Full class name (e.g., 'TriangularMF', 'GaussianMF') - Lowercase class name (e.g., 'triangularmf', 'gaussianmf') - Standard alias (e.g., 'trimf', 'gaussmf', 'sigmoid') Case-insensitive matching is supported. Returns ------- Type[MembershipFunction] The membership function class corresponding to the given name. Raises ------ ValueError If the specified name is not found in the registry. The error message includes a complete list of available function names and aliases. Examples -------- Get classes using different naming conventions: .. code-block:: python # Using full class names TriClass = get_mf_class('TriangularMF') GaussClass = get_mf_class('GaussianMF') # Using MATLAB-style aliases TriClass = get_mf_class('trimf') GaussClass = get_mf_class('gaussmf') # Using lowercase class names TriClass = get_mf_class('triangularmf') GaussClass = get_mf_class('gaussianmf') # Case-insensitive matching TriClass = get_mf_class('TRIMF') TriClass = get_mf_class('TriangularMF') TriClass = get_mf_class('triangularmf') Instantiate classes directly: .. code-block:: python # Get class and create instances MFClass = get_mf_class('trimf') mf1 = MFClass(a=0, b=0.3, c=0.6) mf2 = MFClass(a=0.4, b=0.7, c=1.0) Error handling: .. code-block:: python try: UnknownClass = get_mf_class('unknown_function') except ValueError as e: print(e) # Shows list of all available functions """ mf_cls = _mf_alias_map.get(name.lower()) if mf_cls is None: available = ", ".join(sorted(_mf_alias_map.keys())) raise ValueError(f"Unknown membership function '{name}'. Available functions are: {available}") return mf_cls
[docs] def create_mf(name: str, **mf_kwargs: Any) -> Tuple[MembershipFunction, Dict[str, Any]]: """ Factory function for creating membership function instances with parameter separation. This function creates a membership function instance of the specified type, automatically handling parameter separation between membership function parameters and other system parameters. It uses introspection to determine which parameters belong to the membership function constructor and which should be passed to other system components. Parameters ---------- name : str Name or alias of the membership function to create. Supports the same naming conventions as :func:`get_mf_class`. **mf_kwargs Mixed parameters that may include: - Parameters for the membership function constructor - Parameters for other system components (returned as unused) Returns ------- tuple of (MembershipFunction, dict) A tuple containing: - **instance** : MembershipFunction The created membership function instance. - **remaining_kwargs** : dict Dictionary of parameters that were not used in the membership function constructor. These can be passed to other system components. Raises ------ ValueError - If the specified function name is not recognized - If the membership function constructor raises validation errors TypeError If required parameters for the membership function are missing Notes ----- The parameter separation works by: 1. Inspecting the target class's ``__init__`` method signature 2. Identifying which parameters are accepted by the constructor 3. Separating input kwargs into constructor and remaining parameters 4. Creating the instance with only the relevant parameters This design enables embedding the factory in larger systems where multiple components share parameter dictionaries. Examples -------- Basic usage with parameter separation: .. code-block:: python # Mixed parameters for different system components all_params = { 'a': 0, 'b': 0.5, 'c': 1, # TriangularMF parameters 'mtype': 'qrofn', # Fuzzy system parameter 'q': 2, # Fuzzy system parameter 'method': 'centroid' # Processing parameter } # Create membership function and separate parameters mf, unused = create_mf('trimf', **all_params) print(type(mf).__name__) # 'TriangularMF' print(unused) # {'mtype': 'qrofn', 'q': 2, 'method': 'centroid'} Creating different membership function types: .. code-block:: python # Triangular membership function tri_mf, _ = create_mf('trimf', a=0, b=0.5, c=1) # Gaussian membership function gauss_mf, _ = create_mf('gaussmf', sigma=1.0, c=0.5) # Trapezoidal membership function trap_mf, _ = create_mf('trapmf', a=0, b=0.2, c=0.8, d=1) # Sigmoid membership function sig_mf, _ = create_mf('sigmoid', k=2.0, c=0.5) Integration with fuzzification systems: .. code-block:: python def create_fuzzifier(mf_type, **params): \"\"\"Example of factory integration in larger system.\"\"\" # Create membership function with parameter separation mf, system_params = create_mf(mf_type, **params) # Extract system parameters mtype = system_params.get('mtype', 'qrofn') q_value = system_params.get('q', 2) method = system_params.get('method', 'direct') # Build complete fuzzification system return FuzzificationEngine( membership_function=mf, target_mtype=mtype, q=q_value, method=method ) # Usage with mixed parameters fuzzifier = create_fuzzifier( 'trimf', a=0, b=0.5, c=1, mtype='qrofn', q=3, method='centroid' ) Error handling and validation: .. code-block:: python # Invalid function name try: mf, _ = create_mf('invalid_name', param=1) except ValueError as e: print(f"Function error: {e}") # Invalid parameters (caught by membership function) try: mf, _ = create_mf('trimf', a=1, b=0.5, c=0) # Invalid order except ValueError as e: print(f"Parameter error: {e}") # Missing required parameters try: mf, _ = create_mf('trimf') # No parameters provided except TypeError as e: print(f"Missing parameters: {e}") Advanced usage with class inspection: .. code-block:: python # Get available parameter names for a function MFClass = get_mf_class('trimf') signature = inspect.signature(MFClass.__init__) param_names = list(signature.parameters.keys()) print(f"TriangularMF parameters: {param_names}") # Output: ['self', 'params', 'a', 'b', 'c'] # Create with only known parameters known_params = {k: v for k, v in all_params.items() if k in param_names} mf, unused = create_mf('trimf', **known_params) Batch creation for multiple functions: .. code-block:: python # Configuration for multiple membership functions mf_configs = [ {'name': 'trimf', 'a': 0, 'b': 0.25, 'c': 0.5}, {'name': 'gaussmf', 'sigma': 0.1, 'c': 0.75}, {'name': 'trapmf', 'a': 0.5, 'b': 0.6, 'c': 0.9, 'd': 1.0} ] # Create all functions mf_instances = [] for config in mf_configs: name = config.pop('name') # Remove name from parameters mf, _ = create_mf(name, **config) mf_instances.append(mf) See Also -------- get_mf_class : Get membership function class without creating instance axisfuzzy.membership.base.MembershipFunction : Base class for all functions axisfuzzy.membership.function : Available membership function implementations """ mf_cls = get_mf_class(name) # Use introspection to determine which parameters belong to the constructor # This allows automatic separation of membership function parameters from # other system parameters in mixed parameter dictionaries constructor_signature = inspect.signature(mf_cls.__init__) mf_params = constructor_signature.parameters.keys() # Separate parameters into those for the membership function and others mf_init_kwargs = {} remaining_kwargs = {} for key, value in mf_kwargs.items(): if key in mf_params: mf_init_kwargs[key] = value else: remaining_kwargs[key] = value # Create the membership function instance with only relevant parameters # Any validation errors will be raised by the specific membership function instance = mf_cls(**mf_init_kwargs) return instance, remaining_kwargs