abstochkin.utils API documentation

# abstochkin.utils

Some utility functions on generating random number streams, measuring a function's runtime, calculating a statistic measuring the goodness of fit when comparing time series data, and performing unit conversion of kinetic parameters.

View Source

      1"""
      2Some utility functions on generating random number streams, measuring a
      3function's runtime, calculating a statistic measuring the goodness of fit
      4when comparing time series data, and performing unit conversion of
      5kinetic parameters.
      6"""
      7
      8#  Copyright (c) 2024-2025, Alex Plakantonakis.
      9#
     10#  This program is free software: you can redistribute it and/or modify
     11#  it under the terms of the GNU General Public License as published by
     12#  the Free Software Foundation, either version 3 of the License, or
     13#  (at your option) any later version.
     14#
     15#  This program is distributed in the hope that it will be useful,
     16#  but WITHOUT ANY WARRANTY; without even the implied warranty of
     17#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18#  GNU General Public License for more details.
     19#
     20#  You should have received a copy of the GNU General Public License
     21#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
     22
     23import functools
     24from time import perf_counter
     25
     26import numpy as np
     27from numpy import random
     28from scipy.constants import N_A
     29
     30
     31def rng_streams(n: int, random_state: int):
     32    """
     33    Generate independent streams of random numbers spawned from the
     34    same initial seed.
     35
     36    Parameters
     37    ----------
     38    n : int
     39        number of generators/streams to generate.
     40    random_state : int, optional
     41        initial seed from which n new seeds are spawned.
     42
     43    Returns
     44    -------
     45    list[PCG64DXSM Generator objects]
     46        List of `n` generators of independent streams of random numbers
     47
     48    Notes
     49    -----
     50    See https://numpy.org/doc/stable/reference/random/parallel.html for more info.
     51
     52    On PCG64DXSM:
     53        - https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html#upgrading-pcg64
     54        - https://numpy.org/doc/stable/reference/random/bit_generators/pcg64dxsm.html
     55
     56    Examples
     57    --------
     58    >>> rng = rng_streams(5)  # make 5 random number generators
     59    >>> a = rng[1].integers(1, 10, 100)
     60    """
     61    ss = random.SeedSequence(random_state)
     62    seeds = ss.spawn(n)
     63
     64    # return [random.default_rng(seed) for seed in seeds]  # PCG64 generator objects
     65
     66    # Return PCG64DXSM generator objects
     67    return [random.Generator(random.PCG64DXSM(seed)) for seed in seeds]
     68
     69
     70def measure_runtime(fcn):
     71    """ Decorator for measuring the duration of a function's execution. """
     72    @functools.wraps(fcn)
     73    def inner(*args, **kwargs):
     74        start = perf_counter()
     75        fcn(*args, **kwargs)
     76        stop = perf_counter()
     77        duration = stop - start
     78        if duration < 60:
     79            msg = f"Simulation Runtime: {duration:.3f} sec"
     80        elif 60 <= duration < 3600:
     81            msg = f"Simulation Runtime: {duration / 60:.3f} min"
     82        else:
     83            msg = f"Simulation Runtime: {duration / 3600:.3f} hr"
     84        print(msg)
     85
     86    return inner
     87
     88
     89def r_squared(actual: np.array, theoretical: np.array) -> float:
     90    """
     91    Compute the coefficient of determination, $R^2$.
     92
     93    In the case of comparing the average AbStochKin-simulated species
     94    trajectory to its deterministic trajectory. Since the latter is only
     95    meaningful for a homogeneous population, R² should be
     96    close to `1` for a simulated homogeneous process.
     97    For a heterogeneous process, it can be interpreted as how close
     98    the simulated trajectory is to the deterministic trajectory of a
     99    *homogeneous* process. In this case, $R^2$ would not be expected
    100    to be close to $1$ and the importance of looking at this metric
    101    is questionable.
    102
    103    Parameters
    104    ----------
    105    actual : numpy.array
    106        Actual data obtained through a simulation.
    107    theoretical : numpy.array
    108        Theoretical data to compare the actual data to.
    109
    110    Returns
    111    -------
    112    float
    113        The coefficient of determination, $R^2$.
    114    """
    115    # sst: total sum of squares for simulation avg trajectory
    116    sst = np.nansum((actual - np.nanmean(actual)) ** 2)
    117    # ssr: sum of square residuals (data vs deterministic prediction)
    118    ssr = np.nansum((actual - theoretical) ** 2)
    119
    120    return 1 - ssr / sst if sst != 0 else np.nan
    121
    122
    123def macro_to_micro(macro_val: float | int | list[float | int, ...] | tuple[float | int, float | int],
    124                   volume: float | int,
    125                   order: int = 0,
    126                   *,
    127                   inverse: bool = False) -> float | list[float, ...] | tuple[float, float]:
    128    """
    129    Convert a kinetic parameter value from macroscopic to microscopic form.
    130
    131    The ABK algorithm uses microscopic kinetic constants, thus necessitating
    132    the conversion of any molar quantities to their microscopic counterpart.
    133    For a kinetic parameter, the microscopic form is interpreted as the number
    134    of transition events per second (or whatever the time unit may be).
    135    For a molar quantity, its microscopic form is the number of particles in
    136    the given volume.
    137
    138    Parameters
    139    ----------
    140    macro_val : float or int
    141        The value of the parameter to be converted, expressed in terms of
    142        molar quantities.
    143    volume : float or int
    144        The volume, in liters, in which the process that the given parameter
    145        value is a descriptor of.
    146    order : int, default: 0
    147        The order of the process whose kinetic parameter is to be converted.
    148        The default value of 0 is for parameters (such as Km or K50) whose
    149        units are molarity.
    150    inverse : bool, default: False
    151        Perform the inverse of this operation. That is, convert
    152        from microscopic to macroscopic form.
    153
    154    Returns
    155    --------
    156    float
    157        A kinetic parameter is returned in units of reciprocal seconds.
    158        A molar quantity is returned as the number of particles in the
    159        given volume.
    160
    161    Notes
    162    -----
    163    - A kinetic parameter for a 1st order process will remain unchanged
    164    because its units are already reciprocal seconds.
    165
    166    Reference
    167    ---------
    168    Plakantonakis, Alex. “Agent-based Kinetics: A Nonspatial Stochastic Method
    169    for Simulating the Dynamics of Heterogeneous Populations.”
    170    OSF Preprints, 26 July 2019. Web. Section 2.1.
    171    """
    172    assert volume > 0, "The volume has to be a positive quantity."
    173    assert order >= 0, "The process order cannot be negative."
    174
    175    denom = (N_A * volume) ** (order - 1) if not inverse else 1 / (N_A * volume) ** (order - 1)
    176
    177    if isinstance(macro_val, (list, tuple)):
    178        assert all([True if val >= 0 else False for val in macro_val]), \
    179            "The parameter values cannot be negative."
    180        micro_vals = [val / denom for val in macro_val]
    181        return micro_vals if isinstance(macro_val, list) else tuple(micro_vals)
    182    else:
    183        assert macro_val >= 0, "The parameter value cannot be negative."
    184        return macro_val / denom

def rng_streams(n: int, random_state: int): View Source

    32def rng_streams(n: int, random_state: int):
    33    """
    34    Generate independent streams of random numbers spawned from the
    35    same initial seed.
    36
    37    Parameters
    38    ----------
    39    n : int
    40        number of generators/streams to generate.
    41    random_state : int, optional
    42        initial seed from which n new seeds are spawned.
    43
    44    Returns
    45    -------
    46    list[PCG64DXSM Generator objects]
    47        List of `n` generators of independent streams of random numbers
    48
    49    Notes
    50    -----
    51    See https://numpy.org/doc/stable/reference/random/parallel.html for more info.
    52
    53    On PCG64DXSM:
    54        - https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html#upgrading-pcg64
    55        - https://numpy.org/doc/stable/reference/random/bit_generators/pcg64dxsm.html
    56
    57    Examples
    58    --------
    59    >>> rng = rng_streams(5)  # make 5 random number generators
    60    >>> a = rng[1].integers(1, 10, 100)
    61    """
    62    ss = random.SeedSequence(random_state)
    63    seeds = ss.spawn(n)
    64
    65    # return [random.default_rng(seed) for seed in seeds]  # PCG64 generator objects
    66
    67    # Return PCG64DXSM generator objects
    68    return [random.Generator(random.PCG64DXSM(seed)) for seed in seeds]

Generate independent streams of random numbers spawned from the same initial seed.

###### Parameters

- n (int): number of generators/streams to generate.
- random_state (int, optional): initial seed from which n new seeds are spawned.

###### Returns

- list[PCG64DXSM Generator objects]: List of `n` generators of independent streams of random numbers

###### Notes

See https://numpy.org/doc/stable/reference/random/parallel.html for more info.

On PCG64DXSM: - https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html#upgrading-pcg64 - https://numpy.org/doc/stable/reference/random/bit_generators/pcg64dxsm.html

###### Examples

    >>> rng = rng_streams(5)  # make 5 random number generators
    >>> a = rng[1].integers(1, 10, 100)

def measure_runtime(fcn): View Source

    71def measure_runtime(fcn):
    72    """ Decorator for measuring the duration of a function's execution. """
    73    @functools.wraps(fcn)
    74    def inner(*args, **kwargs):
    75        start = perf_counter()
    76        fcn(*args, **kwargs)
    77        stop = perf_counter()
    78        duration = stop - start
    79        if duration < 60:
    80            msg = f"Simulation Runtime: {duration:.3f} sec"
    81        elif 60 <= duration < 3600:
    82            msg = f"Simulation Runtime: {duration / 60:.3f} min"
    83        else:
    84            msg = f"Simulation Runtime: {duration / 3600:.3f} hr"
    85        print(msg)
    86
    87    return inner

Decorator for measuring the duration of a function's execution.

def r_squared( actual: <built-in function array>, theoretical: <built-in function array>) -> float: View Source

     90def r_squared(actual: np.array, theoretical: np.array) -> float:
     91    """
     92    Compute the coefficient of determination, $R^2$.
     93
     94    In the case of comparing the average AbStochKin-simulated species
     95    trajectory to its deterministic trajectory. Since the latter is only
     96    meaningful for a homogeneous population, R² should be
     97    close to `1` for a simulated homogeneous process.
     98    For a heterogeneous process, it can be interpreted as how close
     99    the simulated trajectory is to the deterministic trajectory of a
    100    *homogeneous* process. In this case, $R^2$ would not be expected
    101    to be close to $1$ and the importance of looking at this metric
    102    is questionable.
    103
    104    Parameters
    105    ----------
    106    actual : numpy.array
    107        Actual data obtained through a simulation.
    108    theoretical : numpy.array
    109        Theoretical data to compare the actual data to.
    110
    111    Returns
    112    -------
    113    float
    114        The coefficient of determination, $R^2$.
    115    """
    116    # sst: total sum of squares for simulation avg trajectory
    117    sst = np.nansum((actual - np.nanmean(actual)) ** 2)
    118    # ssr: sum of square residuals (data vs deterministic prediction)
    119    ssr = np.nansum((actual - theoretical) ** 2)
    120
    121    return 1 - ssr / sst if sst != 0 else np.nan

Compute the coefficient of determination, $R^2$.

In the case of comparing the average AbStochKin-simulated species trajectory to its deterministic trajectory. Since the latter is only meaningful for a homogeneous population, R² should be close to `1` for a simulated homogeneous process. For a heterogeneous process, it can be interpreted as how close the simulated trajectory is to the deterministic trajectory of a homogeneous process. In this case, $R^2$ would not be expected to be close to $1$ and the importance of looking at this metric is questionable.

###### Parameters

- actual (numpy.array): Actual data obtained through a simulation.
- theoretical (numpy.array): Theoretical data to compare the actual data to.

###### Returns

- float: The coefficient of determination, $R^2$.

def macro_to_micro( macro_val: float | int | list[float | int, ...] | tuple[float | int, float | int], volume: float | int, order: int = 0, *, inverse: bool = False) -> float | list[float, ...] | tuple[float, float]: View Source

    124def macro_to_micro(macro_val: float | int | list[float | int, ...] | tuple[float | int, float | int],
    125                   volume: float | int,
    126                   order: int = 0,
    127                   *,
    128                   inverse: bool = False) -> float | list[float, ...] | tuple[float, float]:
    129    """
    130    Convert a kinetic parameter value from macroscopic to microscopic form.
    131
    132    The ABK algorithm uses microscopic kinetic constants, thus necessitating
    133    the conversion of any molar quantities to their microscopic counterpart.
    134    For a kinetic parameter, the microscopic form is interpreted as the number
    135    of transition events per second (or whatever the time unit may be).
    136    For a molar quantity, its microscopic form is the number of particles in
    137    the given volume.
    138
    139    Parameters
    140    ----------
    141    macro_val : float or int
    142        The value of the parameter to be converted, expressed in terms of
    143        molar quantities.
    144    volume : float or int
    145        The volume, in liters, in which the process that the given parameter
    146        value is a descriptor of.
    147    order : int, default: 0
    148        The order of the process whose kinetic parameter is to be converted.
    149        The default value of 0 is for parameters (such as Km or K50) whose
    150        units are molarity.
    151    inverse : bool, default: False
    152        Perform the inverse of this operation. That is, convert
    153        from microscopic to macroscopic form.
    154
    155    Returns
    156    --------
    157    float
    158        A kinetic parameter is returned in units of reciprocal seconds.
    159        A molar quantity is returned as the number of particles in the
    160        given volume.
    161
    162    Notes
    163    -----
    164    - A kinetic parameter for a 1st order process will remain unchanged
    165    because its units are already reciprocal seconds.
    166
    167    Reference
    168    ---------
    169    Plakantonakis, Alex. “Agent-based Kinetics: A Nonspatial Stochastic Method
    170    for Simulating the Dynamics of Heterogeneous Populations.”
    171    OSF Preprints, 26 July 2019. Web. Section 2.1.
    172    """
    173    assert volume > 0, "The volume has to be a positive quantity."
    174    assert order >= 0, "The process order cannot be negative."
    175
    176    denom = (N_A * volume) ** (order - 1) if not inverse else 1 / (N_A * volume) ** (order - 1)
    177
    178    if isinstance(macro_val, (list, tuple)):
    179        assert all([True if val >= 0 else False for val in macro_val]), \
    180            "The parameter values cannot be negative."
    181        micro_vals = [val / denom for val in macro_val]
    182        return micro_vals if isinstance(macro_val, list) else tuple(micro_vals)
    183    else:
    184        assert macro_val >= 0, "The parameter value cannot be negative."
    185        return macro_val / denom

Convert a kinetic parameter value from macroscopic to microscopic form.

The ABK algorithm uses microscopic kinetic constants, thus necessitating the conversion of any molar quantities to their microscopic counterpart. For a kinetic parameter, the microscopic form is interpreted as the number of transition events per second (or whatever the time unit may be). For a molar quantity, its microscopic form is the number of particles in the given volume.

###### Parameters

- macro_val (float or int): The value of the parameter to be converted, expressed in terms of molar quantities.
- volume (float or int): The volume, in liters, in which the process that the given parameter value is a descriptor of.
- order : int, default (0): The order of the process whose kinetic parameter is to be converted. The default value of 0 is for parameters (such as Km or K50) whose units are molarity.
- inverse : bool, default (False): Perform the inverse of this operation. That is, convert from microscopic to macroscopic form.

###### Returns

- float: A kinetic parameter is returned in units of reciprocal seconds. A molar quantity is returned as the number of particles in the given volume.

###### Notes

- A kinetic parameter for a 1st order process will remain unchanged because its units are already reciprocal seconds.

###### Reference

Plakantonakis, Alex. “Agent-based Kinetics: A Nonspatial Stochastic Method for Simulating the Dynamics of Heterogeneous Populations.” OSF Preprints, 26 July 2019. Web. Section 2.1.