hofa.char
=========

.. py:module:: hofa.char

.. autoapi-nested-parse::

   This module contains the Numpy implementation for performing higher-order Fourier analysis 
   using the spectral approach developed by Candela, González-Sánchez, and Szegedy.

   Conventions
   -----------
   The following conventions are used for functions and data structures in this file:

   - Any finite abelian group :math:`Z` is isomorphic to :math:`Z/n_1Z \times Z/n_2Z \times ... \times Z/n_kZ`. Therefore, a function 
     :math:`f: Z \to \mathbb{C}` is represented as a `numpy.ndarray` tensor with shape :math:`(n_1, n_2, ..., n_k)`.
     
   - A :math:`Z`-matrix, where :math:`Z = Z/n_1Z \times ... \times Z/n_kZ`, is represented as a `numpy.ndarray` tensor with shape 
     :math:`(n_1, ..., n_k, n_1, ..., n_k)`.



Attributes
----------

.. autoapisummary::

   hofa.char.logger


Classes
-------

.. autoapisummary::

   hofa.char.RandomSeparator
   hofa.char.StandardRandomSeparator
   hofa.char.RandomizedSeparationResult
   hofa.char.RandomizedSearchCallback
   hofa.char.PrintRandomizedSearchCallback


Functions
---------

.. autoapisummary::

   hofa.char.spechoft
   hofa.char.find_middle_point_of_largest_gap
   hofa.char.eigenvals_separated
   hofa.char.random_complex_unit_vector
   hofa.char.individual_eigenvals_separated


Module Contents
---------------

.. py:data:: logger

   Logger for this module.

   This module-level logger is used to log messages related to the functionality
   provided by this module. It follows the standard Python logging convention,
   where the logger name is set to the module's name (``__name__``).

   The logger can be used to output messages at various severity levels:
   - DEBUG: Detailed information, typically of interest only when diagnosing problems.
   - INFO: Confirmation that things are working as expected.

   To configure the logging behavior (e.g., log level, output format, handlers),
   users should configure the Python logging system at the application level.
   For example:

   :example:
       >>> import logging
       >>> # Configure basic logging to console
       >>> logging.basicConfig(level=logging.INFO)

   :note: The actual output and visibility of log messages depends on the logging
          configuration set by the application using this module. By default,
          if no logging configuration is set, messages at WARNING level and above
          will be sent to stderr.


.. py:class:: RandomSeparator

   Bases: :py:obj:`abc.ABC`


   Abstract base class for an abstract separator of higher order characters.

   This abstract class provides with an interface for the basic functionality
   to look for the higher-order components of a function using a randomized
   algorithm.


   .. py:method:: initial_threshold(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult) -> float
      :abstractmethod:


      Compute the initial threshold for the given input and regularization result.

      This method should be implemented by subclasses to define how the initial
      threshold is determined based on the input data and the first regularization 
      outcome.

      :param f: Input array for which the threshold is computed.
      :type f: np.ndarray
      :param reg_res: Result of a regularization process of ``f``.
      :type reg_res: hofa.rgz.RegularizationResult
      :return: The computed threshold.
      :rtype: float

      :note: Subclasses must override this method. See :py:func:`StandardRandomSeparator.initial_threshold`.



   .. py:method:: iteration_threshold(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult, initial_f: numpy.ndarray, initial_reg_res: hofa.rgz.RegularizationResult) -> float
      :abstractmethod:


      Compute the threshold to be used in the loop for finding the higher order characters.

      This method should be implemented by subclasses to define how the iterative
      threshold is determined based on the iterative regularized function
      and the iterative regularization of such function.

      :param f: Input array for which the threshold is computed.
      :type f: np.ndarray
      :param reg_res: Result of a regularization process of the iteration.
      :type reg_res: rgz.RegularizationResult
      :return: The computed threshold.
      :rtype: float

      :note: Subclasses must override this method. See :py:func:`StandardRandomSeparator.iteration_threshold`.



   .. py:method:: separation_gap(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult, initial_f: numpy.ndarray, initial_reg_res: hofa.rgz.RegularizationResult) -> float
      :abstractmethod:


      Compute the gap required between top eigenvalues.

      This method should be implemented by subclasses to define how the iterative
      gap is determined based on the iterative regularized function
      and the iterative regularization of such function.

      :param f: Input array for which the threshold is computed.
      :type f: np.ndarray
      :param reg_res: Result of a regularization process of the iteration.
      :type reg_res: rgz.RegularizationResult
      :return: The computed gap.
      :rtype: float

      :note: Subclasses must override this method. See :py:func:`StandardRandomSeparator.separation_gap`.



   .. py:method:: get_rng()
      :abstractmethod:


      Get the random number generator (RNG) instance used by this object.

      This abstract method should be implemented by subclasses to return the
      :class:`numpy.random.Generator` instance that is used internally for all
      randomized operations. This allows users to inspect the RNG state or use the
      same RNG for other operations to ensure reproducibility.

      The returned RNG should be the same instance used internally by the object for
      all randomized operations, such as generating random vectors or initializing
      iterative processes.

      :return: The random number generator instance used by this object.
      :rtype: numpy.random.Generator

      :note: Subclasses must override this method to return the RNG instance used
             internally. The RNG should be properly initialized during the object's
             construction (e.g., in :meth:`__init__`).

      :raises NotImplementedError: If the method is not overridden in a subclass.



   .. py:method:: max_iter(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult) -> int
      :abstractmethod:


      Get the maximum number of iterations for the iterative process.

      This abstract method should be implemented by subclasses to define how the
      maximum number of iterations is determined based on the input data `f` and
      the regularization result `reg_res`. The returned value controls the maximum
      number of iterations allowed in iterative algorithms (e.g., during eigenvalue
      separation or character refinement).

      Subclasses may use `f` and `reg_res` to dynamically determine the maximum
      iterations, or they may return a fixed value set during initialization.

      :param f: Input data array, which may be used to determine the maximum iterations.
      :type f: numpy.ndarray
      :param reg_res: Regularization result object, which may be used to determine
                      the maximum iterations. This may include residuals, parameters,
                      or other metadata from the regularization process.
      :type reg_res: rgz.RegularizationResult

      :return: The maximum number of iterations to perform in iterative processes.
      :rtype: int

      :note: Subclasses must override this method. The implementation should define
             how the maximum iterations are determined, either dynamically (based on
             `f` and `reg_res`) or as a fixed value. For an example implementation, see
             :py:meth:`StandardRandomSeparator.max_iter`.

      :raises NotImplementedError: If the method is not overridden in a subclass.



   .. py:method:: iterative_search() -> bool
      :abstractmethod:


      Check whether the algorithm should use iterative search.

      This abstract method should be implemented by subclasses to return a boolean
      indicating whether the algorithm should use an iterative approach for separation
      or a non-iterative (direct) method. This flag controls the overall strategy of the
      algorithm, where iterative search is generally faster and similarly accurate.

      The returned value should be determined based on the algorithm's configuration
      (e.g., parameters set during initialization) and the specific requirements of
      the subclass implementation.

      :return: True if iterative search is enabled, False if a non-iterative method
               should be used.
      :rtype: bool

      :note: Subclasses must override this method. The implementation should return
             the value of the iterative search flag, which is typically set during
             initialization. For an example implementation, see
             :py:meth:`StandardRandomSeparator.iterative_search`.

      :raises NotImplementedError: If the method is not overridden in a subclass.



.. py:class:: StandardRandomSeparator(param_initial: float | int = 1.2, mode_initial: str = 'dynamic-relative', param_iter: float | int = 1.2, mode_iter: str = 'dynamic-relative', param_separation: float | int = 0.6, mode_separation: str = 'dynamic', max_iterations: int = 20, iterative_search: bool = True, rng: int | numpy.random.Generator | None = None)

   Bases: :py:obj:`RandomSeparator`


   A standard implementation of :class:`RandomSeparator`.

   This constructor sets up the parameters and modes for the initialization,
   iteration, and separation phases of the algorithm. The values of ``param_initial``,
   ``param_iter``, and ``param_separation`` must be positive numbers.

   The values of ``mode_initial``, ``mode_iter``, and ``mode_separation`` must
   be one of the following. In the sequel, let :math:`\sigma^2` be the variance
   of the input function

       - 'dynamic-relative': 
                            Let :math:`\rho` be the product of ``param`` multiplied by :math:`\sigma\sqrt{|Z|/\log|Z|}`.
                            The parameter (the threshold or gap) will be then the middle
                            point of the largest gap of the eigenvalues of the 
                            regularization in the interval :math:`[\rho/2,\rho]`.
       - 'dynamic-strict': 
                          The parameter (the threshold or gap) will be
                          ``param`` multiplied by :math:`\sigma\sqrt{|Z|/\log|Z|}`.
       - 'literal-relative':
                            The parameter (the threshold or gap) will be the middle
                            point of the largest gap of the eigenvalues of the 
                            regularization in the interval :math:`[\text{param}/2,\text{param}]`.
       - 'literal-strict': 
                           The parameter will be ``param``.


   :param param_initial: Initial parameter value for the first phase of the algorithm.
                         Default is 1.2.
   :type param_initial: float or int
   :param mode_initial: Mode for the initialization phase. 
   :type mode_initial: str
   :param param_iter: Parameter value for the iteration phase of the algorithm.
                      Default is 1.2.
   :type param_iter: float or int
   :param mode_iter: Mode for the iteration phase. 
   :type mode_iter: str
   :param param_separation: Parameter value for the separation phase of the algorithm. Default is 1.2.
   :type param_separation: float or int
   :param mode_separation: Mode for the separation phase. 
   :type mode_separation: str
   :param random_state: Seed for the random number generator or a
                        :class:`numpy.random.Generator` instance for reproducibility.
                        If None, the global random state is used.
   :type random_state: int or numpy.random.Generator or None

   :note: The 'dynamic-relative' mode is the default for all phases and is recommended
          for most use cases. Other modes may require specific parameter values.

   :example:
       >>> separator = StandardRandomSeparator(
       ...     param_initial=1.5,
       ...     mode_initial='dynamic-strict'
       ... )


   .. py:attribute:: param_initial
      :value: 1.2



   .. py:attribute:: mode_initial
      :value: 'dynamic-relative'



   .. py:attribute:: param_iter
      :value: 1.2



   .. py:attribute:: mode_iter
      :value: 'dynamic-relative'



   .. py:attribute:: param_separation
      :value: 0.6



   .. py:attribute:: mode_separation
      :value: 'dynamic'



   .. py:attribute:: max_iterations
      :value: 20



   .. py:attribute:: do_iterative_search
      :value: True



   .. py:attribute:: group_size
      :value: 0



   .. py:method:: initial_threshold(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult) -> float

      Compute the initial threshold value based on input data and regularization result.

      This method calculates an initial threshold for the eigenvalues of the algorithm.
      The threshold is determined based on the
      input data array `f` and the result of the regularization process contained in
      `reg_res`. Depending on the values of `self.param_initial` and `self.mode_initial`,
      the threshold is computed according to the rules described in :class:`StandardRandomSeparator`.

      :param f: Input data array for which the threshold is computed.
      :type f: numpy.ndarray
      :param reg_res: Regularization result object containing information needed
                      to compute the threshold.
      :type reg_res: rgz.RegularizationResult

      :return: The computed initial threshold value.
      :rtype: float



   .. py:method:: iteration_threshold(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult, initial_f: numpy.ndarray, initial_reg_res: hofa.rgz.RegularizationResult) -> float

      Compute the iteration threshold value based on input data and regularization result.

      This method calculates an iteration threshold for the eigenvalues.
      The threshold is determined based on the
      input data array `f` and the result of the regularization process contained in
      `reg_res`. Depending on the values of `self.param_iteration` and `self.mode_iteration`,
      the threshold is computed according to the rules described in :class:`StandardRandomSeparator`.

      :param f: Input data array for which the threshold is computed.
      :type f: numpy.ndarray
      :param reg_res: Regularization result object containing information needed
                      to compute the threshold.
      :type reg_res: rgz.RegularizationResult

      :return: The computed initial threshold value.
      :rtype: float



   .. py:method:: separation_gap(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult, initial_f: numpy.ndarray, initial_reg_res: hofa.rgz.RegularizationResult) -> float

      Compute the separation gap value based on input data and regularization result.

      This method calculates an separation gap for the eigenvalues.
      The threshold is determined based on the
      input data array `f` and the result of the regularization process contained in
      `reg_res`. Depending on the values of `self.param_separation` and `self.mode_separation`,
      the threshold is computed according to the rules described in :class:`StandardRandomSeparator`.

      :param f: Input data array for which the threshold is computed.
      :type f: numpy.ndarray
      :param reg_res: Regularization result object containing information needed
                      to compute the threshold.
      :type reg_res: rgz.RegularizationResult

      :return: The computed initial threshold value.
      :rtype: float



   .. py:method:: get_rng()

      Get the random number generator (RNG) instance used by this object.

      This method returns the :class:`numpy.random.Generator` instance that is used
      internally for all randomized operations.

      :return: The random number generator instance used by this object.
      :rtype: numpy.random.Generator

      :note: The returned RNG is the same instance used internally by this object.
              Modifying its state (e.g., by calling its methods) may affect the reproducibility
              of this object's operations.



   .. py:method:: max_iter(f: numpy.ndarray, reg_res: hofa.rgz.RegularizationResult) -> int

      Get the maximum number of iterations for the iterative process.

      This method returns the maximum number of iterations (`self.max_iterations`)
      that will be used in iterative algorithms (e.g., during eigenvalue separation).
      The input parameters `f` and `reg_res` are accepted for consistency with the 
      abstract :class:`RandomSeparator` but are not used in this implementation.

      :param f: Input data array. This parameter is accepted for consistency but is not used.
      :type f: numpy.ndarray
      :param reg_res: Regularization result object. This parameter is accepted for consistency
                      but is not used.
      :type reg_res: rgz.RegularizationResult

      :return: The maximum number of iterations, as set during initialization.
      :rtype: int

      :note: This method is designed to be overridden in subclasses to allow dynamic
             determination of the maximum iterations based on `f` and `reg_res`.
             In this base implementation, it simply returns `self.max_iterations`.



   .. py:method:: iterative_search() -> bool

      Check whether the algorithm should use iterative search.

      This method returns the value of `self.do_iterative_search`, which determines
      whether the algorithm will use an iterative approach for separation or a
      non-iterative (direct) method.

      :return: True if iterative search is enabled, False otherwise.
      :rtype: bool

      :note: This flag is typically set during initialization and controls whether the
             algorithm will iterate to refine the separation (if True) or try to
             find all eigenvectors at once (if False). Iterative search is generally 
             faster and similarly accurate.



.. py:class:: RandomizedSeparationResult

   Bases: :py:obj:`NamedTuple`


   A container for the results of a randomized separation algorithm.

   This ``NamedTuple`` stores the outputs of a randomized algorithm that separates
   higher-order characters and eigenvalues. It provides information about the
   separation process, including whether the separation was successful, the
   threshold used, and the number of iterations performed.

   :ivar higher_order_char: The computed higher-order characters after separation.
   :vartype higher_order_char: numpy.ndarray

   :ivar separated_eigenvalues: The eigenvalues obtained after separation.
                                 This is a 1D numpy array.
   :vartype separated_eigenvalues: numpy.ndarray

   :ivar are_separated: Boolean flag indicating whether the separation was successful.
                        True if the characters and eigenvalues were successfully separated
                        according to the algorithm's criteria, False otherwise.
   :vartype are_separated: bool

   :ivar separation_threshold: The threshold value used for the separation process.
                                This value determines the criterion for successful separation.
   :vartype separation_threshold: float

   :ivar total_iterations: The total number of iterations performed by the separation
                           algorithm before termination.
   :vartype total_iterations: int

   :note: This NamedTuple is typically returned by functions that perform randomized
          separation of higher-order characters and eigenvalues. The `are_separated` flag
          should be checked to verify if the separation was successful before using
          the results.

   :example:
       >>> # Assuming 'result' is returned by a separation function
       >>> if result.are_separated:
       ...     print("Separation successful!")
       ...     print("Higher-order characters:", result.higher_order_char)
       ...     print("Eigenvalues:", result.separated_eigenvalues)
       ... else:
       ...     print("Separation failed after", result.total_iterations, "iterations")


   .. py:attribute:: higher_order_char
      :type:  numpy.ndarray


   .. py:attribute:: separated_eigenvalues
      :type:  numpy.ndarray


   .. py:attribute:: are_separated
      :type:  bool


   .. py:attribute:: separation_threshold
      :type:  float


   .. py:attribute:: total_iterations
      :type:  int


.. py:class:: RandomizedSearchCallback

   Bases: :py:obj:`abc.ABC`


   Abstract base class for callback objects used during randomized search algorithms.

   This class defines the interface for callback objects that can be used to monitor
   the progress of the randomized search algorithm at every iteration. Subclasses 
   should implement the callback methods to perform custom actions.


   .. py:method:: on_iteration(iteration_count: int, eigenvalues_found: int, number_eigenvales_separated: int, iteration_rho: float, separation_delta: float, f_reg_iteration: hofa.rgz.RegularizationResult, rnd_separator: RandomSeparator, number_of_eig_already_found: int, target_number_eigenvalues: int)

      Callback method invoked after each iteration of the randomized search algorithm.

      This method is called at each iteration just after computing the regularization
      of the particular function from which we want to obtain the higher order
      characters, providing information about the current state of the algorithm. 
      Subclasses should override this method to implement custom behavior, 
      such as printing progress updates, logging metrics, or updating visualizations.

      :param iteration_count: The current iteration index (0-based).
      :type iteration_count: int
      :param eigenvalues_found: The total number of eigenvalues found so far.
      :type eigenvalues_found: int
      :param number_eigenvales_separated: The number of eigenvalues successfully separated
                                           in the current iteration.
      :type number_eigenvales_separated: int
      :param iteration_rho: The threshold used during the current iteration.
      :type iteration_rho: float
      :param separation_delta: The separation gap or threshold used during the current iteration.
      :type separation_delta: float
      :param f_reg_iteration: The regularization result object for the current iteration.
      :type f_reg_iteration: rgz.RegularizationResult
      :param rnd_separator: The random separator object.
      :type rnd_separator: RandomSeparator
      :param number_of_eig_already_found: The cumulative number of eigenvalues found
                                           before this iteration.
      :type number_of_eig_already_found: int
      :param target_number_eigenvalues: The target number of eigenvalues to find.
      :type target_number_eigenvalues: int

      :note: Subclasses must override this method to implement custom behavior.
             The base implementation does nothing.



   .. py:method:: on_end(iteration_count: int, rnd_separator: RandomSeparator, number_of_eig_already_found: int, target_number_eigenvalues: int)

      Callback method invoked at the end of the randomized search algorithm.

      This method is called once when the algorithm terminates, providing a summary
      of the final state. Subclasses should override this method to implement custom
      behavior, such as printing a summary, logging final results, or cleaning up resources.

      :param iteration_count: The total number of iterations performed.
      :type iteration_count: int
      :param rnd_separator: The random separator object used during the final iteration.
      :type rnd_separator: RandomSeparator
      :param number_of_eig_already_found: The total number of eigenvalues found by the algorithm.
      :type number_of_eig_already_found: int
      :param target_number_eigenvalues: The target number of eigenvalues to find.
      :type target_number_eigenvalues: int

      :note: Subclasses must override this method to implement custom behavior.
             The base implementation does nothing.



.. py:class:: PrintRandomizedSearchCallback

   Bases: :py:obj:`RandomizedSearchCallback`


   A concrete callback class that prints progress updates during randomized search.

   This class implements the :class:`RandomizedSearchCallback` interface to provide
   real-time progress updates during the execution of a randomized search algorithm.
   It prints information about each iteration and a summary at the end of the process,
   allowing users to monitor the algorithm's progress directly in the console.

   The output is formatted to update on a single line (using ``\r``) during iterations,
   providing a clean and compact progress display. At the end of the process, a summary
   line is printed with the final results.

   :example:
       >>> callback = PrintRandomizedSearchCallback()
       >>> # Pass the callback to your randomized search function
       >>> result = spechoft(f, callback=callback)


   .. py:method:: on_iteration(iteration_count: int, eigenvalues_found: int, number_eigenvales_separated: int, iteration_rho: float, separation_delta: float, f_reg_iteration: hofa.rgz.RegularizationResult, rnd_separator: RandomSeparator, number_of_eig_already_found: int, target_number_eigenvalues: int)

      Print progress information for the current iteration.

      This method prints a compact progress update for each iteration of the
      randomized search algorithm. The output is formatted to overwrite the
      previous line (using ``\r``), creating a dynamic progress display in the console.

      The printed information includes:
      - Current iteration count
      - Number of eigenvalues found in this iteration
      - Number of eigenvalues separated in this iteration
      - Whether iterative search is enabled
      - Cumulative number of eigenvalues found so far
      - Target number of eigenvalues

      :param iteration_count: Current iteration index (0-based).
      :type iteration_count: int
      :param eigenvalues_found: Number of eigenvalues found in this iteration.
      :type eigenvalues_found: int
      :param number_eigenvales_separated: Number of eigenvalues successfully separated
                                           in this iteration.
      :type number_eigenvales_separated: int
      :param iteration_rho: Current value of the rho parameter (not printed but available).
      :type iteration_rho: float
      :param separation_delta: Current separation delta (not printed but available).
      :type separation_delta: float
      :param f_reg_iteration: Regularization result for this iteration (not used but available).
      :type f_reg_iteration: rgz.RegularizationResult
      :param rnd_separator: Random separator object used in this iteration.
      :type rnd_separator: RandomSeparator
      :param number_of_eig_already_found: Cumulative number of eigenvalues found so far.
      :type number_of_eig_already_found: int
      :param target_number_eigenvalues: Target number of eigenvalues to find.
      :type target_number_eigenvalues: int

      :note: The output is printed with ``end='\r'``, which causes the text to overwrite
             the previous line in most terminals. This creates a dynamic progress display
             that updates in place rather than scrolling.



   .. py:method:: on_end(iteration_count: int, rnd_separator: RandomSeparator, number_of_eig_already_found: int, target_number_eigenvalues: int)

      Print a summary of the randomized search results at the end of the process.

      This method prints a summary line with the final results of the randomized
      search algorithm, including:
      - Total number of iterations performed
      - Whether iterative search was used
      - Total number of eigenvalues found
      - Target number of eigenvalues

      Unlike the iteration updates, this summary is printed as a new line to ensure
      it remains visible after the process completes.

      :param iteration_count: Total number of iterations performed.
      :type iteration_count: int
      :param rnd_separator: Random separator object used in the final iteration.
      :type rnd_separator: RandomSeparator
      :param number_of_eig_already_found: Total number of eigenvalues found.
      :type number_of_eig_already_found: int
      :param target_number_eigenvalues: Target number of eigenvalues to find.
      :type target_number_eigenvalues: int

      :note: This method prints a new line (without ``\r``) to ensure the summary
             remains visible in the console after the process completes.



.. py:function:: spechoft(f: numpy.ndarray, order: int | List[hofa.rgz.LayerRegularizer] = 2, rnd_separator: RandomSeparator | None = None, iteration_regularizers: List[hofa.rgz.LayerRegularizer] | None = None, rng: numpy.random.Generator | int | None = None, callback: RandomizedSearchCallback | None = None) -> RandomizedSeparationResult

   Find higher-order characters using a randomized algorithm with customizable regularization.

   This function implements a randomized algorithm to identify higher-order characters
   in the input data using a separation approach. We refer the reader to the online 
   documentation for a more detailed account on the theory behind this algorithm.

   The process roughly involves:
   1. Initial separation using the provided random separator and initial regularizers.
   2. Iterative trial an error until we find a separating function using the iteration-phase regularizers.
   3. Termination when an appropriate function is found or when max_iterations is reached.

   The function returns a comprehensive result object containing the separated
   higher-order characters, eigenvalues, and metadata about the separation process.

   :param f: Input data array for which to find higher-order characters.
             The function from which to obtain the higher order characters.
   :type f: numpy.ndarray
   :param order: Order of the higher-order characters to compute, or a list of layer regularizers.
                 Can be either:
                 - An integer specifying the order (default is 2).
                 - A list of :class:`rgz.LayerRegularizer` objects for more fine-grained control.
                 Default is 2.
   :type order: int or List[rgz.LayerRegularizer]
   :param rnd_separator: Random separator object to use for the separation process.
                         If None, a default random separator will be created.
                         The separator defines the strategy for separating eigenvalues and characters.
   :type rnd_separator: RandomSeparator or None
   :param iteration_regularizers: List of layer regularizers to use during the iteration phase.
                                   If None, a copy of the regularizers given in the ``order``
                                   variable is used.
   :type iteration_regularizers: List[rgz.LayerRegularizer] or None
   :param rng: Random number generator or seed for reproducibility.
               If ``None`` parameters are used for the variables ``order`` and ``iteration_regularizers``,
               then this random number generator will be used to generate them,
               ensuring predictable outcome if the ``rng`` is either a :class:`numpy.random.Generator`
               instance or an integer.
               Can be either:
               - A :class:`numpy.random.Generator` instance.
               - An integer seed (which will be used to create a Generator).
               - None, in which case the global random state is used.
               Default is None.
   :type rng: numpy.random.Generator or int or None
   :param callback: Optional callback object for monitoring progress.
                    Should be an instance of :class:`RandomizedSearchCallback`.
                    If provided, the callback's methods will be invoked during the algorithm's execution.
                    Default is None.
   :type callback: RandomizedSearchCallback or None

   :return: A named tuple containing the results of the spectral decomposition.
            The result includes:
            - higher_order_char: The computed higher-order characters.
            - separated_eigenvalues: The separated eigenvalues.
            - are_separated: Boolean indicating if separation was successful.
            - separation_threshold: The threshold used for separation.
            - total_iterations: Number of iterations performed.
   :rtype: RandomizedSeparationResult

   :note: The quality of the results depends on the choice of random separator and
          regularizers. The 'dynamic-relative' mode in the random separator often
          provides a good balance between speed and accuracy.

          If per_layer_regularizers_initial or per_layer_regularizers_iteration is an
          integer, default regularizers will be created for each layer.

          For reproducible results, provide a fixed seed or Generator for the rng parameter.

   :example:
       >>> import numpy as np
       >>> import hofa.char
       >>>
       >>> # Create input data
       >>> n = 501
       >>> f = np.sin(2*np.pi*x**2/n)
       >>>
       >>> # Run the algorithm with default parameters
       >>> result = hofa.char.spechoft(f)
       >>>
       >>> # Check if separation was successful
       >>> if result.are_separated:
       ...     print("Separation successful!")
       ...     print("Higher-order characters: ", result.higher_order_char)
       ... else:
       ...     print("Separation failed after ", result.total_iterations, " iterations")


.. py:function:: find_middle_point_of_largest_gap(x_array: numpy.ndarray, lower_end: float, upper_end: float) -> float

   Find the midpoint of the largest gap in a filtered array within specified bounds.

   This auxiliary function identifies the largest gap between consecutive elements in a filtered
   version of the input array, where only elements within the range [lower_end, upper_end]
   are considered. The midpoint of this largest gap is then returned. 

   :param x_array: Input array of values. Can contain any real numbers.
   :type x_array: numpy.ndarray
   :param lower_end: Lower bound of the range to consider. Values in x_array below this are ignored.
                     Its value must be lower than ``upper_end``, undefined behaviour otherwise.
   :type lower_end: float
   :param upper_end: Upper bound of the range to consider. Values in x_array above this are ignored.
                     Its value must be higher than ``lower_end``, undefined behaviour otherwise.
   :type upper_end: float

   :return: The midpoint of the largest gap in the filtered and extended array.
            This is returned as a Python float scalar.
   :rtype: float

   :note: If all elements in x_array are outside the [lower_end, upper_end] range,
          the function will return the midpoint between lower_end and upper_end.
          If there are no gaps (all elements are identical), the function will
          return one of the boundary points.

   :example:
       >>> import numpy as np
       >>> import hofa.char
       >>> x = np.array([1.0, 2.0, 3.5, 4.0, 6.0, 7.0, 11.0])
       >>> c = hofa.char.find_middle_point_of_largest_gap(x, 3.0, 7.0)
       >>> print(c)
       5.0


.. py:function:: eigenvals_separated(eig_vals, delta) -> bool

   Check if eigenvalues are sufficiently separated based on a minimum gap threshold.

   This function determines whether the eigenvalues in the input array are separated by
   at least the specified threshold `delta`. It computes the gaps between consecutive
   eigenvalues and checks if all gaps are greater than `delta`.

   If there are fewer than 2 eigenvalues, the function returns `True` by convention,
   as separation is trivially satisfied.

   :param eig_vals: 1D array of eigenvalues, sorted in ascending order.
                    The function assumes the input is sorted; if not, results may be incorrect.
   :type eig_vals: numpy.ndarray
   :param delta: Minimum required gap between consecutive eigenvalues.
                 If all gaps are greater than `delta`, the eigenvalues are considered separated.
   :type delta: float

   :return: True if all gaps between consecutive eigenvalues are greater than `delta`,
            or if there are fewer than 2 eigenvalues. False otherwise.
   :rtype: bool

   :note: This function assumes that `eig_vals` is sorted in ascending order.
          If the input is not sorted, call `np.sort(eig_vals)` first or sort the array
          before passing it to this function.

   :example:
       >>> import numpy as np
       >>> import hofa.char
       >>> eig_vals = np.array([1.0, 2.5, 4.0, 5.5])
       >>> print(hofa.char.eigenvals_separated(eig_vals, 1.0))  # True, all gaps > 1.0
       True
       >>> print(hofa.char.eigenvals_separated(eig_vals, 1.5))  # False, gap between 2.5 and 4.0 is 1.5 (not > 1.5)
       False
       >>> print(hofa.char.eigenvals_separated(np.array([1.0]), 1.0))  # True, fewer than 2 eigenvalues
       True


.. py:function:: random_complex_unit_vector(rng: numpy.random.Generator, dim: int) -> numpy.ndarray

   Generate a random complex vector with unit :math:`\ell^2` norm.

   This function creates a random unit complex vector of the specified dimension.

   :param rng: Random number generator instance for reproducibility.
               Use :func:`numpy.random.default_rng` to create a generator with a seed
               for reproducible results.
   :type rng: numpy.random.Generator
   :param dim: Dimension of the complex vector to generate, must be at least 1.
   :type dim: int

   :return: A complex vector of shape (dim,) with unit norm.
            Each element is a complex number with real and imaginary parts
            drawn from a standard normal distribution.
   :rtype: numpy.ndarray

   :note: The returned vector has a unit :math:`\ell^2` norm, meaning that the sum of the
          squared magnitudes of all components equals 1.

   :example:
       >>> import numpy as np
       >>> import hofa.char
       >>> rng = np.random.default_rng(seed=42)  # For reproducibility
       >>> vec = hofa.char.random_complex_unit_vector(rng, dim=5)
       >>> print(vec.shape) 
       (5,)
       >>> print(np.isclose(np.linalg.norm(vec), 1.0))
       True


.. py:function:: individual_eigenvals_separated(eigenvals: numpy.ndarray, threshold: float) -> numpy.ndarray

   Return a boolean mask indicating which eigenvalues are separated from their neighbors by at least `threshold`.

   Args:
       eigenvals: 1D array of shape (n,) containing real numbers in increasing order.
       threshold: Minimum distance required for an eigenvalue to be considered separated.

   Returns:
       np.ndarray: Boolean mask of shape (n,), where res[i] is True if eigenvals[i] is separated from its neighbors.


