Source code for flowstab

"""
Flow Stability: Temporal Network Flow-Based Clustering and Analysis


This package provides tools for stability analysis and clustering of temporal networks
using flow-based methods. The core component is the :class:`FlowStability` class,
which implements a reproducible, state-tracked analytical pipeline for temporal (contact sequence)
data. The package also includes logging utilities to facilitate debugging and reproducible research.

Main Features
-------------
- Load and process temporal networks (contact sequences) from files or arrays.
- Compute Laplacian and inter-transition matrices for random walks on temporal networks.
- Extract flow-based clusterings using methods such as the Louvain algorithm.
- Track analysis progress and enforce correct computational order through a robust state machine.
- Configurable logging for both development and production.

Logging
-------
The package sets up a default logger on import. You can adjust the logging level:

>>> import flowstab
>>> flowstab.set_log_level("DEBUG")

Usage Example
-------------
A typical workflow for analyzing a temporal network and extracting clusters:

>>> from flowstab import FlowStability
>>> fs = FlowStability()
>>> fs.set_temporal_network(event_table="my_contacts.csv")
>>> fs.set_time_scale(10)
>>> fs.compute_laplacian_matrices()
>>> fs.compute_inter_transition_matrices()
>>> fs.time_direction = 0
>>> fs.set_flow_clustering()
>>> fs.find_louvain_clustering()
>>> print(fs.flow_clustering_forward)

This workflow ensures that all computational steps are performed in the required order;
the state machine will warn or prevent you from skipping prerequisites.

If you are unsure what method needs to be run next and/or what parameter needs
to be set in the current state run `fs.state.next`.
This will return a `tuple` providing a list of parameters to set, and the name
of the next method to run:

>>> fs.state.next
>>> ['t_start', ..], 'set_temporal_network'

To learn more about a parameter or method you might use `fs.state.info` or
`fs.state.howto`:

>>> print(fs.state.info['time_scale'])
>>> Time scales used for random walk transition rates.
>>> 
>>> Returns
>>> -------
>>> iterator
>>>     Iterator over the time scales. Each value determines the rate of the
>>>     random walks transitions.
>>> 
>>> .. note::
>>>     Single values are alos returned as an iterator.

>>> print(fs.state.howto['time_scale'])
>>> Set the time scale(s) for the random walks transition rate.
>>> 
>>> .. note::
>>>     You might also use `set_time_scale` to directly create a range of
>>>     time scales.
>>> 
>>> Parameters
>>> ----------
>>> time_scale : None, int, float, or iterator of float
>>>     If None, a default value is used.
>>>     If an int or float, a single time scale is set.
>>>     If an iterator, it must yield float or int values.
>>> 
>>> Raises
>>> ------
>>> TypeError
>>>     If the input is not None, int, float, or an iterator of numbers.



Module Contents
---------------
- FlowStability
    Main class for performing flow stability analysis and clustering.
- set_log_level
    Function to set the global logging level for the package.

Author
------
Alexandre Bovet <alexandre.bovet@maths.ox.ac.uk>


Contributors
............

- Jonas I. Liechti <j-i-l@t4d.ch>

License
-------
GNU Lesser General Public License v3 or later (LGPLv3+).

"""
try:
    # try to import version (provided by hatch (see pyproject.toml)
    from ._version import __version__
except ImportError:
    # Fallback if the package wasn't installed properly
[docs] __version__ = "unknown"
import logging from .flow_stability import FlowStability from .logger import setup_logger, get_logger # Default log level setup_logger() # Set up the logger with the default level
[docs] def set_log_level(level): """ Set the logging level for the package. Parameters ---------- level : str The logging level as a string (e.g., 'DEBUG', 'INFO'). """ level_dict = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL, } if level in level_dict: logger = get_logger() logger.setLevel(level_dict[level]) for handler in logger.handlers: handler.setLevel(level_dict[level]) else: raise ValueError(f"Invalid log level: {level}. Choose from {list(level_dict.keys())}.")