bs-python-utils 1.0__tar.gz → 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/PKG-INFO +8 -1
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/README.md +7 -0
- bs_python_utils-1.0.1/bs_python_utils/core/Timer.py +81 -0
- bs_python_utils-1.0.1/bs_python_utils/core/__init__.py +1 -0
- bs_python_utils-1.0.1/bs_python_utils/core/bs_logging.py +168 -0
- bs_python_utils-1.0.1/bs_python_utils/core/bs_mathstr.py +121 -0
- bs_python_utils-1.0.1/bs_python_utils/core/bs_mem.py +157 -0
- bs_python_utils-1.0.1/bs_python_utils/core/bsutils.py +410 -0
- bs_python_utils-1.0.1/bs_python_utils/data_anal/__init__.py +1 -0
- bs_python_utils-1.0.1/bs_python_utils/data_anal/pandas_utils.py +245 -0
- bs_python_utils-1.0.1/bs_python_utils/data_anal/sklearn_utils.py +123 -0
- bs_python_utils-1.0.1/bs_python_utils/numerical/__init__.py +1 -0
- bs_python_utils-1.0.1/bs_python_utils/numerical/bs_sparse_gaussian.py +74 -0
- bs_python_utils-1.0.1/bs_python_utils/numerical/bsnputils.py +1221 -0
- bs_python_utils-1.0.1/bs_python_utils/numerical/bssputils.py +82 -0
- bs_python_utils-1.0.1/bs_python_utils/numerical/chebyshev.py +550 -0
- bs_python_utils-1.0.1/bs_python_utils/opt/__init__.py +1 -0
- bs_python_utils-1.0.1/bs_python_utils/opt/bs_opt.py +576 -0
- bs_python_utils-1.0.1/bs_python_utils/stats/__init__.py +1 -0
- bs_python_utils-1.0.1/bs_python_utils/stats/bivariate_quantiles.py +276 -0
- bs_python_utils-1.0.1/bs_python_utils/stats/bsstats.py +502 -0
- bs_python_utils-1.0.1/bs_python_utils/stats/distance_covariances.py +263 -0
- bs_python_utils-1.0.1/bs_python_utils/viz/__init__.py +1 -0
- bs_python_utils-1.0.1/bs_python_utils/viz/bs_altair.py +1111 -0
- bs_python_utils-1.0.1/bs_python_utils/viz/bs_plots.py +62 -0
- bs_python_utils-1.0.1/bs_python_utils/viz/bs_seaborn.py +210 -0
- bs_python_utils-1.0.1/bs_python_utils/viz/bsmplutils.py +95 -0
- bs_python_utils-1.0.1/bs_python_utils/viz/streamlit_utils.py +100 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils.egg-info/PKG-INFO +8 -1
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils.egg-info/SOURCES.txt +26 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/pyproject.toml +4 -3
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_random_draws.py +2 -2
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/LICENSE +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid1prec13.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid1prec17.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid1prec9.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid2prec13.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid2prec17.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid2prec9.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid3prec13.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid3prec17.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid3prec9.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid4prec13.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid4prec17.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid4prec9.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid5prec13.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid5prec17.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/GaussHermiteSparseGrids/GHsparseGrid5prec9.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/Timer.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/__init__.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bivariate_quantiles.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_altair.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_logging.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_mathstr.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_mem.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_opt.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_plots.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_seaborn.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bs_sparse_gaussian.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bsmplutils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bsnputils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bssputils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bsstats.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/bsutils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/chebyshev.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/distance_covariances.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_altair.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_distance_covariances.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_mem.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_mpl.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_opt.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_seaborn.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_sklearn.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/examples/examples_sparse_gaussian.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/pandas_utils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/sklearn_utils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils/streamlit_utils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils.egg-info/dependency_links.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils.egg-info/requires.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/bs_python_utils.egg-info/top_level.txt +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/setup.cfg +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bivariate_quantiles.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bs_opt.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bs_sparse_gaussian.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bsnputils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bssputils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bsstats.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_bsutils.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_chebyshev.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_nptri.py +0 -0
- {bs_python_utils-1.0 → bs_python_utils-1.0.1}/tests/test_sklearn_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bs-python-utils
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Utilities programs for my Python code
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -31,9 +31,13 @@ Dynamic: license-file
|
|
|
31
31
|
## bs-python-utils
|
|
32
32
|
|
|
33
33
|
[](https://pypi.org/project/bs-python-utils/)
|
|
34
|
+
<!--
|
|
34
35
|
[](https://github.com/bsalanie/bs-python-utils/releases)
|
|
36
|
+
-->
|
|
35
37
|
[](https://github.com/bsalanie/bs-python-utils/actions/workflows/main.yml?query=branch%3Amain)
|
|
38
|
+
<!--
|
|
36
39
|
[](https://github.com/bsalanie/bs-python-utils/actions/workflows/tests.yml?query=branch%3Amain)
|
|
40
|
+
-->
|
|
37
41
|
[](https://github.com/bsalanie/bs-python-utils/commits/main)
|
|
38
42
|

|
|
39
43
|
[](https://img.shields.io/github/license/bsalanie/bs-python-utils)
|
|
@@ -45,6 +49,9 @@ Dynamic: license-file
|
|
|
45
49
|
|
|
46
50
|
### Release notes
|
|
47
51
|
|
|
52
|
+
#### 1.0.1 (May 9, 2026)
|
|
53
|
+
Fixed badges.
|
|
54
|
+
|
|
48
55
|
#### 1.0 (May 9, 2026)
|
|
49
56
|
Created subpackages (no change in the API).
|
|
50
57
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
## bs-python-utils
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/bs-python-utils/)
|
|
4
|
+
<!--
|
|
4
5
|
[](https://github.com/bsalanie/bs-python-utils/releases)
|
|
6
|
+
-->
|
|
5
7
|
[](https://github.com/bsalanie/bs-python-utils/actions/workflows/main.yml?query=branch%3Amain)
|
|
8
|
+
<!--
|
|
6
9
|
[](https://github.com/bsalanie/bs-python-utils/actions/workflows/tests.yml?query=branch%3Amain)
|
|
10
|
+
-->
|
|
7
11
|
[](https://github.com/bsalanie/bs-python-utils/commits/main)
|
|
8
12
|

|
|
9
13
|
[](https://img.shields.io/github/license/bsalanie/bs-python-utils)
|
|
@@ -15,6 +19,9 @@
|
|
|
15
19
|
|
|
16
20
|
### Release notes
|
|
17
21
|
|
|
22
|
+
#### 1.0.1 (May 9, 2026)
|
|
23
|
+
Fixed badges.
|
|
24
|
+
|
|
18
25
|
#### 1.0 (May 9, 2026)
|
|
19
26
|
Created subpackages (no change in the API).
|
|
20
27
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities to time code:
|
|
3
|
+
|
|
4
|
+
* a `Timer` class that can be used as a context manager
|
|
5
|
+
* a `timeit` decorator for functions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from functools import wraps
|
|
10
|
+
from typing import Any, Callable, Iterable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def timeit(func: Callable) -> Callable:
|
|
14
|
+
"""
|
|
15
|
+
Decorator to time a function
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@wraps(func)
|
|
19
|
+
def wrapper(*args: Iterable, **kwargs: dict) -> Any:
|
|
20
|
+
start = time.perf_counter()
|
|
21
|
+
result = func(*args, **kwargs)
|
|
22
|
+
end = time.perf_counter()
|
|
23
|
+
print(f"{func.__name__} executed in {end - start:.3f} seconds")
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
return wrapper
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Timer:
|
|
30
|
+
"""
|
|
31
|
+
A timer that can be started, stopped, and reset as needed by the user.
|
|
32
|
+
It keeps track of the total elapsed time in the `elapsed` attribute::
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> with Timer() as t:
|
|
36
|
+
>>> ....
|
|
37
|
+
>>> print(f"... took {t.elapsed} seconds")
|
|
38
|
+
|
|
39
|
+
use `Timer(time.process_time)` to get only CPU time.
|
|
40
|
+
|
|
41
|
+
can also do:
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
>>> t = Timer()
|
|
45
|
+
>>> t.start()
|
|
46
|
+
>>> t.stop()
|
|
47
|
+
>>> t.start() # will add to the same counter
|
|
48
|
+
>>> t.stop()
|
|
49
|
+
>>> print(f"{t.elapsed} seconds total")
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, func: Callable = time.perf_counter) -> None:
|
|
53
|
+
self.elapsed = 0.0
|
|
54
|
+
self._func = func
|
|
55
|
+
self._start = None
|
|
56
|
+
|
|
57
|
+
def start(self) -> None:
|
|
58
|
+
if self._start is not None:
|
|
59
|
+
raise RuntimeError("Already started")
|
|
60
|
+
self._start = self._func()
|
|
61
|
+
|
|
62
|
+
def stop(self) -> None:
|
|
63
|
+
if self._start is None:
|
|
64
|
+
raise RuntimeError("Not started")
|
|
65
|
+
end = self._func()
|
|
66
|
+
self.elapsed += end - self._start
|
|
67
|
+
self._start = None
|
|
68
|
+
|
|
69
|
+
def reset(self) -> None:
|
|
70
|
+
self.elapsed = 0.0
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def running(self) -> bool:
|
|
74
|
+
return self._start is not None
|
|
75
|
+
|
|
76
|
+
def __enter__(self) -> Any:
|
|
77
|
+
self.start()
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def __exit__(self, *args: Iterable) -> None:
|
|
81
|
+
self.stop()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# core: general utilities — bsutils, Timer, bs_logging, bs_mathstr, bs_mem
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Utilities for logging:
|
|
2
|
+
|
|
3
|
+
* `get_logger` creates and returns a logger with colored output
|
|
4
|
+
* `init_logger` initializes and returns a customized logger
|
|
5
|
+
* `log_execution` is a decorator to log entry into and ext from a function.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
from colorama import Fore, Style, init
|
|
15
|
+
|
|
16
|
+
# Initialize colorama
|
|
17
|
+
init(autoreset=True)
|
|
18
|
+
|
|
19
|
+
COLOR_MAP = {
|
|
20
|
+
"DEBUG": Fore.CYAN,
|
|
21
|
+
"INFO": Fore.GREEN,
|
|
22
|
+
"WARNING": Fore.YELLOW,
|
|
23
|
+
"ERROR": Fore.RED,
|
|
24
|
+
"CRITICAL": Fore.MAGENTA,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ColorFormatter(logging.Formatter):
|
|
29
|
+
def format(self, record):
|
|
30
|
+
# Mirror the standard Formatter logic so we still get message, asctime, and exception text
|
|
31
|
+
record.message = record.getMessage()
|
|
32
|
+
if self.usesTime():
|
|
33
|
+
record.asctime = self.formatTime(record, self.datefmt)
|
|
34
|
+
|
|
35
|
+
color = COLOR_MAP.get(record.levelname, "")
|
|
36
|
+
level = f"{color}{record.levelname}{Style.RESET_ALL}"
|
|
37
|
+
output = f"{record.asctime} [{record.name}] {level}: {record.message}"
|
|
38
|
+
|
|
39
|
+
if record.exc_info and not record.exc_text:
|
|
40
|
+
record.exc_text = self.formatException(record.exc_info)
|
|
41
|
+
if record.exc_text:
|
|
42
|
+
output = f"{output}\n{record.exc_text}"
|
|
43
|
+
|
|
44
|
+
return output
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_logger(
|
|
48
|
+
name: str,
|
|
49
|
+
level: int = logging.INFO,
|
|
50
|
+
log_to_file: bool = False,
|
|
51
|
+
) -> logging.Logger:
|
|
52
|
+
"""Create and return a logger with colored output.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: Name of the logger.
|
|
56
|
+
level: Minimum logging level.
|
|
57
|
+
log_to_file: If True, also log to a file named '{name}.log'.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Configured logger instance.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
>>> logger = get_logger("my_logger", level=logging.DEBUG, log_to_file=True)
|
|
64
|
+
>>> logger.debug("This is a debug message.")
|
|
65
|
+
"""
|
|
66
|
+
logger = logging.getLogger(name)
|
|
67
|
+
if logger.handlers:
|
|
68
|
+
return logger # Already configured
|
|
69
|
+
logger.setLevel(level)
|
|
70
|
+
formatter = ColorFormatter("%(asctime)s", datefmt="%H:%M:%S")
|
|
71
|
+
stream_handler = logging.StreamHandler(sys.stdout)
|
|
72
|
+
stream_handler.setFormatter(formatter)
|
|
73
|
+
logger.addHandler(stream_handler)
|
|
74
|
+
if log_to_file:
|
|
75
|
+
file_handler = logging.FileHandler(f"{name}.log")
|
|
76
|
+
file_handler.setFormatter(formatter)
|
|
77
|
+
logger.addHandler(file_handler)
|
|
78
|
+
return logger
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def init_logger(
|
|
82
|
+
logger_name: str,
|
|
83
|
+
log_level_for_console: str = "info",
|
|
84
|
+
log_level_for_file: str = "debug",
|
|
85
|
+
save_dir: str | None = None,
|
|
86
|
+
) -> logging.Logger:
|
|
87
|
+
"""
|
|
88
|
+
Initialize a logger
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
logger_name: name for the logger
|
|
92
|
+
log_level_for_console: minimum level of messages logged to the
|
|
93
|
+
console logging
|
|
94
|
+
log_level_for_file:
|
|
95
|
+
save_dir:
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
the logger
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
>>> logger_dir = "logs"
|
|
102
|
+
>>> logger_name = "check_log"
|
|
103
|
+
>>> logger = init_logger(logger_name, save_dir=logger_dir)
|
|
104
|
+
|
|
105
|
+
This will create two logs:
|
|
106
|
+
|
|
107
|
+
* one printed to console where we run the code (the
|
|
108
|
+
`StreamHandler`),
|
|
109
|
+
* and one that will be saved to file `save_dir/logger_name.txt`
|
|
110
|
+
(the `FileHandler`).
|
|
111
|
+
|
|
112
|
+
`'logger.propagate = False'` makes sure that the logs sent to file
|
|
113
|
+
will not be printed to console.
|
|
114
|
+
|
|
115
|
+
We use the `Formatter` class to define the format of the logs.
|
|
116
|
+
Here:
|
|
117
|
+
|
|
118
|
+
* The time of the log in a human-readable format, `asctime`
|
|
119
|
+
* `levelname` is the level of the log, one out of `INFO, DEBUG, WARNING, ERROR, CRITICAL`.
|
|
120
|
+
* The name of the file, `filename`, from which the log was generated,
|
|
121
|
+
and the line number, `lineno`.
|
|
122
|
+
* Lastly, the message itself — `message`.
|
|
123
|
+
|
|
124
|
+
The default has only `INFO` logs and above (i.e., also
|
|
125
|
+
`WARNING, ERROR` and `CRITICAL`)
|
|
126
|
+
displayed in the console; the file will also include `DEBUG` logs.
|
|
127
|
+
"""
|
|
128
|
+
logger = logging.getLogger()
|
|
129
|
+
logger.setLevel(level=logging.DEBUG)
|
|
130
|
+
logger.propagate = False
|
|
131
|
+
|
|
132
|
+
formatter = logging.Formatter(
|
|
133
|
+
"%(asctime)s [%(levelname)s] %(filename)s %(lineno)d - %(message)s",
|
|
134
|
+
"%Y-%m-%d %H:%M:%S",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
ch = logging.StreamHandler()
|
|
138
|
+
ch.setLevel(log_level_for_console.upper())
|
|
139
|
+
ch.setFormatter(formatter)
|
|
140
|
+
logger.addHandler(ch)
|
|
141
|
+
|
|
142
|
+
if save_dir is not None:
|
|
143
|
+
Path(save_dir).mkdir(exist_ok=True, parents=True)
|
|
144
|
+
fh = logging.FileHandler(save_dir + f"/{logger_name}.txt")
|
|
145
|
+
fh.setLevel(log_level_for_file.upper())
|
|
146
|
+
fh.setFormatter(formatter)
|
|
147
|
+
logger.addHandler(fh)
|
|
148
|
+
|
|
149
|
+
return logger
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def log_execution(func: Callable) -> Callable:
|
|
153
|
+
"""Decorator to log the execution of a function.
|
|
154
|
+
Only records entry to and exit from the function, to the console.
|
|
155
|
+
"""
|
|
156
|
+
loglevel = logging.info
|
|
157
|
+
|
|
158
|
+
@functools.wraps(func)
|
|
159
|
+
def wrapper(*args, **kwargs):
|
|
160
|
+
loglevel(f"Executing {func.__name__}")
|
|
161
|
+
result = func(*args, **kwargs)
|
|
162
|
+
loglevel(f"Finished executing {func.__name__}")
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
return wrapper
|
|
166
|
+
|
|
167
|
+
return wrapper
|
|
168
|
+
return wrapper
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Some useful strings for math formulae.
|
|
2
|
+
|
|
3
|
+
Note:
|
|
4
|
+
if the math looks strange in the documentation, just reload the page.
|
|
5
|
+
|
|
6
|
+
Attributes:
|
|
7
|
+
* str_beta0: the LaTeX string $\\beta_0$
|
|
8
|
+
* str_beta1: the LaTeX string $\\beta_1$
|
|
9
|
+
* str_pi: the LaTeX string $\\pi$
|
|
10
|
+
* str_sigma: the LaTeX string $\\sigma$
|
|
11
|
+
* str_sigma2: the LaTeX string $\\sigma^2$
|
|
12
|
+
* uni_beta0: the Unicode string $\\beta_0$
|
|
13
|
+
* uni_beta1: the Unicode string $\\beta_1$
|
|
14
|
+
* uni_pi: the Unicode string $\\pi$
|
|
15
|
+
* uni_sigma: the Unicode string $\\sigma$
|
|
16
|
+
* uni_sigma2: the Unicode string $\\sigma^2$
|
|
17
|
+
* uni_s2: the Unicode string $s^2$
|
|
18
|
+
* uni_R2: the Unicode string $R^2$
|
|
19
|
+
* sub_sub_scripts: a dictionary of unicodes for subscripts and superscripts;
|
|
20
|
+
e.g $a^b$ would be `"a" + sub_sup_scripts['b'][0]`
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# LaTeX strings
|
|
24
|
+
str_beta0 = r"$\beta_0$"
|
|
25
|
+
str_beta1 = r"$\beta_1$"
|
|
26
|
+
str_pi = r"$\pi$"
|
|
27
|
+
str_sigma = r"$\sigma$"
|
|
28
|
+
str_sigma2 = r"$\sigma^2$"
|
|
29
|
+
|
|
30
|
+
# * unicode
|
|
31
|
+
uni_beta0 = "\N{GREEK SMALL LETTER BETA}\N{SUBSCRIPT ZERO}"
|
|
32
|
+
uni_beta1 = "\N{GREEK SMALL LETTER BETA}\N{SUBSCRIPT ONE}"
|
|
33
|
+
uni_pi = "\N{GREEK SMALL LETTER PI}"
|
|
34
|
+
uni_sigma = "\N{GREEK SMALL LETTER SIGMA}"
|
|
35
|
+
uni_sigma2 = "\N{GREEK SMALL LETTER SIGMA}\N{SUPERSCRIPT TWO}"
|
|
36
|
+
uni_s2 = "s\N{SUPERSCRIPT TWO}"
|
|
37
|
+
uni_R2 = "R\N{SUPERSCRIPT TWO}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
sub_sup_scripts = {
|
|
41
|
+
# superscript subscript
|
|
42
|
+
"0": ("\u2070", "\u2080"),
|
|
43
|
+
"1": ("\u00b9", "\u2081"),
|
|
44
|
+
"2": ("\u00b2", "\u2082"),
|
|
45
|
+
"3": ("\u00b3", "\u2083"),
|
|
46
|
+
"4": ("\u2074", "\u2084"),
|
|
47
|
+
"5": ("\u2075", "\u2085"),
|
|
48
|
+
"6": ("\u2076", "\u2086"),
|
|
49
|
+
"7": ("\u2077", "\u2087"),
|
|
50
|
+
"8": ("\u2078", "\u2088"),
|
|
51
|
+
"9": ("\u2079", "\u2089"),
|
|
52
|
+
"a": ("\u1d43", "\u2090"),
|
|
53
|
+
"b": ("\u1d47", "?"),
|
|
54
|
+
"c": ("\u1d9c", "?"),
|
|
55
|
+
"d": ("\u1d48", "?"),
|
|
56
|
+
"e": ("\u1d49", "\u2091"),
|
|
57
|
+
"f": ("\u1da0", "?"),
|
|
58
|
+
"g": ("\u1d4d", "?"),
|
|
59
|
+
"h": ("\u02b0", "\u2095"),
|
|
60
|
+
"i": ("\u2071", "\u1d62"),
|
|
61
|
+
"j": ("\u02b2", "\u2c7c"),
|
|
62
|
+
"k": ("\u1d4f", "\u2096"),
|
|
63
|
+
"l": ("\u02e1", "\u2097"),
|
|
64
|
+
"m": ("\u1d50", "\u2098"),
|
|
65
|
+
"n": ("\u207f", "\u2099"),
|
|
66
|
+
"o": ("\u1d52", "\u2092"),
|
|
67
|
+
"p": ("\u1d56", "\u209a"),
|
|
68
|
+
"q": ("?", "?"),
|
|
69
|
+
"r": ("\u02b3", "\u1d63"),
|
|
70
|
+
"s": ("\u02e2", "\u209b"),
|
|
71
|
+
"t": ("\u1d57", "\u209c"),
|
|
72
|
+
"u": ("\u1d58", "\u1d64"),
|
|
73
|
+
"v": ("\u1d5b", "\u1d65"),
|
|
74
|
+
"w": ("\u02b7", "?"),
|
|
75
|
+
"x": ("\u02e3", "\u2093"),
|
|
76
|
+
"y": ("\u02b8", "?"),
|
|
77
|
+
"z": ("?", "?"),
|
|
78
|
+
"A": ("\u1d2c", "?"),
|
|
79
|
+
"B": ("\u1d2e", "?"),
|
|
80
|
+
"C": ("?", "?"),
|
|
81
|
+
"D": ("\u1d30", "?"),
|
|
82
|
+
"E": ("\u1d31", "?"),
|
|
83
|
+
"F": ("?", "?"),
|
|
84
|
+
"G": ("\u1d33", "?"),
|
|
85
|
+
"H": ("\u1d34", "?"),
|
|
86
|
+
"I": ("\u1d35", "?"),
|
|
87
|
+
"J": ("\u1d36", "?"),
|
|
88
|
+
"K": ("\u1d37", "?"),
|
|
89
|
+
"L": ("\u1d38", "?"),
|
|
90
|
+
"M": ("\u1d39", "?"),
|
|
91
|
+
"N": ("\u1d3a", "?"),
|
|
92
|
+
"O": ("\u1d3c", "?"),
|
|
93
|
+
"P": ("\u1d3e", "?"),
|
|
94
|
+
"Q": ("?", "?"),
|
|
95
|
+
"R": ("\u1d3f", "?"),
|
|
96
|
+
"S": ("?", "?"),
|
|
97
|
+
"T": ("\u1d40", "?"),
|
|
98
|
+
"U": ("\u1d41", "?"),
|
|
99
|
+
"V": ("\u2c7d", "?"),
|
|
100
|
+
"W": ("\u1d42", "?"),
|
|
101
|
+
"X": ("?", "?"),
|
|
102
|
+
"Y": ("?", "?"),
|
|
103
|
+
"Z": ("?", "?"),
|
|
104
|
+
"+": ("\u207a", "\u208a"),
|
|
105
|
+
"-": ("\u207b", "\u208b"),
|
|
106
|
+
"=": ("\u207c", "\u208c"),
|
|
107
|
+
"(": ("\u207d", "\u208d"),
|
|
108
|
+
")": ("\u207e", "\u208e"),
|
|
109
|
+
":alpha": ("\u1d45", "?"),
|
|
110
|
+
":beta": ("\u1d5d", "\u1d66"),
|
|
111
|
+
":gamma": ("\u1d5e", "\u1d67"),
|
|
112
|
+
":delta": ("\u1d5f", "?"),
|
|
113
|
+
":epsilon": ("\u1d4b", "?"),
|
|
114
|
+
":theta": ("\u1dbf", "?"),
|
|
115
|
+
":iota": ("\u1da5", "?"),
|
|
116
|
+
":pho": ("?", "\u1d68"),
|
|
117
|
+
":phi": ("\u1db2", "?"),
|
|
118
|
+
":psi": ("\u1d60", "\u1d69"),
|
|
119
|
+
":chi": ("\u1d61", "\u1d6a"),
|
|
120
|
+
":coffee": ("\u2615", "\u2615"),
|
|
121
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Reports on memory usage:
|
|
2
|
+
|
|
3
|
+
* `mem_usage`: prints the top `n` largest global items in memory
|
|
4
|
+
* `memory_display_top`: prints the top `n` largest memory allocations
|
|
5
|
+
since tracing started
|
|
6
|
+
* `memory_display_top_diffs`: prints the top `n` largest memory
|
|
7
|
+
allocations since the last snapshot.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import linecache
|
|
11
|
+
import sys
|
|
12
|
+
import tracemalloc
|
|
13
|
+
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
from bs_python_utils.core.bsutils import print_stars
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _obj_size_fmt(num: int) -> str:
|
|
20
|
+
"""
|
|
21
|
+
format sizes from bytes to appropriate strings depending on size
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
num: size of object in bytes
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
its formatted size
|
|
28
|
+
"""
|
|
29
|
+
if num < 10**3:
|
|
30
|
+
return "{:.2f}{}".format(num, "B")
|
|
31
|
+
elif (num >= 10**3) & (num < 10**6):
|
|
32
|
+
return "{:.2f}{}".format(num / (1.024 * 10**3), "KB")
|
|
33
|
+
elif (num >= 10**6) & (num < 10**9):
|
|
34
|
+
return "{:.2f}{}".format(num / (1.024 * 10**6), "MB")
|
|
35
|
+
else:
|
|
36
|
+
return "{:.2f}{}".format(num / (1.024 * 10**9), "GB")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def memory_usage(n: int | None = 10) -> None:
|
|
40
|
+
"""
|
|
41
|
+
prints the top `n` largest global items in memory
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
n: we report the size of the largest `n` global items
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
nothing.
|
|
48
|
+
"""
|
|
49
|
+
memory_usage_by_variable = pd.DataFrame(
|
|
50
|
+
{k: sys.getsizeof(v) for (k, v) in globals().items()}, index=["Size"]
|
|
51
|
+
)
|
|
52
|
+
memory_usage_by_variable = memory_usage_by_variable.T
|
|
53
|
+
total_usage = _obj_size_fmt(memory_usage_by_variable["Size"].sum())
|
|
54
|
+
memory_usage_by_variable = memory_usage_by_variable.sort_values(
|
|
55
|
+
by="Size", ascending=False
|
|
56
|
+
).head(n)
|
|
57
|
+
memory_usage_by_variable["Size"] = memory_usage_by_variable["Size"].apply(
|
|
58
|
+
lambda x: _obj_size_fmt(x)
|
|
59
|
+
)
|
|
60
|
+
print_stars(
|
|
61
|
+
f"Currently used memory = {total_usage}\n\t\t\t\t Top {n} global objects:"
|
|
62
|
+
)
|
|
63
|
+
print(memory_usage_by_variable)
|
|
64
|
+
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def memory_display_top(
|
|
69
|
+
snapshot: tracemalloc.Snapshot, key_type: str = "lineno", limit: int | None = 5
|
|
70
|
+
) -> None:
|
|
71
|
+
"""
|
|
72
|
+
prints out the lines with the top `limit` allocations of memory since
|
|
73
|
+
`tracemalloc.start()`
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
snapshot: obtained from tracemalloc.take_snapshot()
|
|
77
|
+
key_type: 'lineno' gives file and line number; 'traceback' gives all
|
|
78
|
+
limit: how many top allocations we want
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
just prints.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
>>> tracemalloc.start()
|
|
85
|
+
>>> .... execute ...
|
|
86
|
+
>>> snapshot = tracemalloc.take_snapshot()
|
|
87
|
+
>>> memory_display_top(snapshot)
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
top_stats = snapshot.statistics(key_type)
|
|
91
|
+
|
|
92
|
+
print_stars(f"Top {limit} memory allocations")
|
|
93
|
+
for index, stat in enumerate(top_stats[:limit], 1):
|
|
94
|
+
frame = stat.traceback[0]
|
|
95
|
+
print(
|
|
96
|
+
"#%s: %s:%s: %.1f KiB"
|
|
97
|
+
% (index, frame.filename, frame.lineno, stat.size / 1024)
|
|
98
|
+
)
|
|
99
|
+
line = linecache.getline(frame.filename, frame.lineno).strip()
|
|
100
|
+
if line:
|
|
101
|
+
print(" %s" % line)
|
|
102
|
+
|
|
103
|
+
other = top_stats[limit:]
|
|
104
|
+
if other:
|
|
105
|
+
size = sum(stat.size for stat in other)
|
|
106
|
+
print(f"{len(other)} other: {size / 1024:.1f} KiB")
|
|
107
|
+
total = sum(stat.size for stat in top_stats)
|
|
108
|
+
print("Total allocated size: %.1f KiB" % (total / 1024))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def memory_display_top_diffs(
|
|
112
|
+
snapshot1: tracemalloc.Snapshot,
|
|
113
|
+
snapshot2: tracemalloc.Snapshot,
|
|
114
|
+
key_type: str = "lineno",
|
|
115
|
+
limit: int = 5,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
prints out the lines with the top `limit` allocations between the two snapshots
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
snapshot1: previous snapshot
|
|
122
|
+
snapshot2: new snapshot
|
|
123
|
+
key_type: 'lineno' gives file and line number; 'traceback' gives all
|
|
124
|
+
limit: how many top allocations we want
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
just prints.
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
>>> tracemalloc.start()
|
|
131
|
+
>>> .... execute ...
|
|
132
|
+
>>> snapshot1 = tracemalloc.take_snapshot()
|
|
133
|
+
>>> .... execute ...
|
|
134
|
+
>>> snapshot2 = tracemalloc.take_snapshot()
|
|
135
|
+
>>> memory_display_top_diffs(snapshot1, snapshot2)
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
top_stats = snapshot2.compare_to(snapshot1, key_type)
|
|
140
|
+
|
|
141
|
+
print_stars(f"Top {limit} new memory allocations")
|
|
142
|
+
for index, stat in enumerate(top_stats[:limit], 1):
|
|
143
|
+
frame = stat.traceback[0]
|
|
144
|
+
print(
|
|
145
|
+
"#%s: %s:%s: %.1f KiB"
|
|
146
|
+
% (index, frame.filename, frame.lineno, stat.size / 1024)
|
|
147
|
+
)
|
|
148
|
+
line = linecache.getline(frame.filename, frame.lineno).strip()
|
|
149
|
+
if line:
|
|
150
|
+
print(" %s" % line)
|
|
151
|
+
|
|
152
|
+
other = top_stats[limit:]
|
|
153
|
+
if other:
|
|
154
|
+
size = sum(stat.size for stat in other)
|
|
155
|
+
print(f"{len(other)} other: {size / 1024:.1f} KiB")
|
|
156
|
+
total = sum(stat.size for stat in top_stats)
|
|
157
|
+
print("Total allocated size: %.1f KiB" % (total / 1024))
|