Skip to content

Commit daa6520

Browse files
authored
fix: 🐛 stop configuring global logging; replace structlog with stdlib logging (#52)
Importing src_method no longer calls logging.basicConfig() or structlog.configure(), so the root logger level and handlers of the host application are left untouched. The package now uses logging.getLogger(__name__) per module with a NullHandler on the top-level logger, emits progress messages at DEBUG with lazy %-style arguments, and drops the structlog runtime dependency and the LOG_LEVEL_SRC variable. Fixes #11 Assisted-by: pi:claude-opus-5.5
1 parent fe38d6d commit daa6520

15 files changed

Lines changed: 167 additions & 104 deletions

File tree

‎AGENTS.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ tensor, so there is no wrapper type.
2323
- Backend-agnostic code: go through `src_method.utils._backend` instead of
2424
importing `numpy` or `cupy` directly in the algorithms, so CPU and GPU paths
2525
stay in sync.
26-
- Log with `structlog` via `src_method.utils.logging_config`, never `print`.
26+
- Log with the standard library: `logger = logging.getLogger(__name__)` per
27+
module, lazy `%`-style arguments, never `print`. Never add handlers, levels
28+
or `basicConfig` in the package; progress messages go at `DEBUG`.
2729
- **Run `uv run prek run --all-files` and the relevant tests before
2830
pushing, and fix every finding.** `lint.yml` runs the same hooks in CI, so a
2931
skipped lint is a red PR.

‎README.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ The result then pairs with a ket by plain contraction, with no further conjugati
8787

8888
At runtime, pass `device="gpu"` to use GPU acceleration. The library handles backend dispatch automatically.
8989

90+
### Logging
91+
92+
`src_method` logs through the standard library `logging` module, under the
93+
`src_method` logger, and stays silent by default: it adds only a `NullHandler`
94+
and never touches the root logger or its handlers. Progress and timing messages
95+
are emitted at `DEBUG`, the small-network fallback at `WARNING`. To see them,
96+
configure logging in your application:
97+
98+
```python
99+
import logging
100+
101+
logging.basicConfig(level=logging.INFO)
102+
logging.getLogger("src_method").setLevel(logging.DEBUG)
103+
```
104+
90105
## Installation
91106

92107
```bash

‎benches/primitives/leonardo/bench_mpo_mpo.py‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22

33
from __future__ import annotations
44

5+
import logging
56
from time import perf_counter_ns
67

78
import cyclopts
89
import numpy as np
910
import quimb.tensor as qtn
10-
import structlog
1111

1212
from src_method import apply
1313

14-
# Set up logger
15-
logger = structlog.get_logger()
14+
logger = logging.getLogger(__name__)
1615

1716

1817
# Initialize cyclopts app
@@ -38,13 +37,13 @@ def main(
3837
array_type = np.complex128
3938

4039
logger.info(
41-
"benchmark_start",
42-
n_sites=n_sites,
43-
chi_out=chi_out,
44-
phys_dim=phys_dim,
45-
dtype="complex128",
46-
run=run,
47-
compare=compare,
40+
"benchmark_start: n_sites=%d, chi_out=%d, phys_dim=%d, dtype=complex128, "
41+
"run=%s, compare=%s",
42+
n_sites,
43+
chi_out,
44+
phys_dim,
45+
run,
46+
compare,
4847
)
4948

5049
# Generate a random MPO and a perturbed identity MPO
@@ -92,4 +91,9 @@ def main(
9291

9392

9493
if __name__ == "__main__":
94+
logging.basicConfig(
95+
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s"
96+
)
97+
# Report the per-sweep timings of src_method too.
98+
logging.getLogger("src_method").setLevel(logging.DEBUG)
9599
app()

‎benches/primitives/leonardo/run.sh‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
#SBATCH --exclusive
1111
#SBATCH --output=logs/1000_%j.out
1212

13-
# Python application's loggin level
14-
export LOG_LEVEL_SRC=DEBUG
15-
1613
# Automatic OpenMP binding
1714
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
1815
export OMP_PLACES=cores

‎benches/stack/README.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ uv run python benches/stack/bench_depth.py accuracy --output accuracy.md
2525
uv run python benches/stack/bench_depth.py timing --output timing.md
2626
```
2727

28-
Tuple options repeat the flag, e.g. `--depths 2 --depths 3`. Set
29-
`LOG_LEVEL_SRC=WARNING` to silence the per-call logs.
28+
Tuple options repeat the flag, e.g. `--depths 2 --depths 3`.
3029

3130
## Results
3231

‎benches/stack/bench_depth.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@
1111

1212
from __future__ import annotations
1313

14+
import logging
1415
from dataclasses import astuple, dataclass, fields
1516
from functools import reduce
1617
from pathlib import Path
1718
from time import perf_counter
1819

1920
import cyclopts
2021
import numpy as np
21-
import structlog
2222
from scipy.linalg import expm
2323

2424
from src_method import apply, src
2525

26-
logger = structlog.get_logger()
26+
logger = logging.getLogger(__name__)
2727
app = cyclopts.App(help="Benchmark one-shot SRC over stacks against pairwise apply.")
2828

2929
X = np.array([[0, 1], [1, 0]], dtype=complex)
@@ -188,7 +188,7 @@ class TimingRow:
188188
def write_table(rows: list, output: Path | None) -> None:
189189
"""Log every row and, if requested, write them as a Markdown table."""
190190
for row in rows:
191-
logger.info("result", **vars(row))
191+
logger.info("result: %s", vars(row))
192192
if output is None:
193193
return
194194
names = [f.name for f in fields(rows[0])]
@@ -323,4 +323,5 @@ def timing(
323323

324324

325325
if __name__ == "__main__":
326+
logging.basicConfig(level=logging.INFO, format="%(message)s")
326327
app()

‎docs/index.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,18 @@ container.
135135
[Visual Studio Code]: https://code.visualstudio.com/
136136
[GitHub CLI]: https://cli.github.com/
137137
[at this link]: https://docs.algorithmiq.fi/src_method
138+
139+
### Logging
140+
141+
`src_method` logs through the standard library `logging` module, under the
142+
`src_method` logger, and stays silent by default: it adds only a `NullHandler`
143+
and never touches the root logger or its handlers. Progress and timing messages
144+
are emitted at `DEBUG`, the small-network fallback at `WARNING`. To see them,
145+
configure logging in your application:
146+
147+
```python
148+
import logging
149+
150+
logging.basicConfig(level=logging.INFO)
151+
logging.getLogger("src_method").setLevel(logging.DEBUG)
152+
```

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ classifiers = [
3232
"Typing :: Typed",
3333
]
3434
dynamic = ["version"]
35-
dependencies = ["numpy>=2.4.4,<3", "opt_einsum>=3.4.0", "structlog>=25.5.0"]
35+
dependencies = ["numpy>=2.4.4,<3", "opt_einsum>=3.4.0"]
3636

3737

3838
[project.optional-dependencies]

‎src/src_method/__init__.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55

66
from __future__ import annotations
77

8+
import logging
9+
810
from ._version import version as __version__
911
from ._version import version_tuple as __version_tuple__
1012
from .apply import apply
1113
from .compress import compress
1214
from .stack import src
1315

16+
# Stay silent unless the application configures logging.
17+
logging.getLogger(__name__).addHandler(logging.NullHandler())
18+
1419
__all__ = ["__version__", "__version_tuple__", "apply", "compress", "src"]

‎src/src_method/_sweep.py‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
from __future__ import annotations
1515

16+
import logging
1617
from functools import cache
1718
from itertools import count
1819
from math import prod
1920
from time import perf_counter_ns
2021
from typing import TYPE_CHECKING, NamedTuple
2122

22-
import structlog
2323
from opt_einsum import contract_expression, get_symbol
2424

2525
from ._tensor_train import pad, unpad
@@ -35,7 +35,7 @@
3535

3636
from ._tensor_train import TrainKind
3737

38-
logger = structlog.get_logger(__name__)
38+
logger = logging.getLogger(__name__)
3939

4040

4141
class _Contractions:
@@ -125,11 +125,13 @@ def sweep(
125125
contract = _Contractions()
126126
# sites[j] holds the padded tensors of every layer at site j.
127127
sites = list(zip(*(pad([xp.asarray(a) for a in layer]) for layer in layers)))
128-
logger.debug(
129-
"Largest environment (elements)",
130-
size=chi_out
131-
* max(prod(t.shape[1] for t in sites[j]) for j in range(n_sites - 1)),
132-
)
128+
# Guarded: the max walks every site, wasted work unless it is logged.
129+
if logger.isEnabledFor(logging.DEBUG):
130+
logger.debug(
131+
"Largest environment: %d elements",
132+
chi_out
133+
* max(prod(t.shape[1] for t in sites[j]) for j in range(n_sites - 1)),
134+
)
133135

134136
tms = perf_counter_ns()
135137
# C[j] is the sketched environment of sites 0 .. j-1.
@@ -138,7 +140,7 @@ def sweep(
138140
up, down = sites[j][0].shape[2], sites[j][-1].shape[3]
139141
omega = gaussian_sketch(prng, (chi_out, up, down), dtype, xp)
140142
C.append(contract(eqs.ltr, C[j], omega, *sites[j]))
141-
logger.debug("Left-to-right sweep", seconds=(perf_counter_ns() - tms) * 1e-9)
143+
logger.debug("Left-to-right sweep: %.3f s", (perf_counter_ns() - tms) * 1e-9)
142144

143145
tms = perf_counter_ns()
144146
eta_reversed: list[NDArray] = []
@@ -153,6 +155,6 @@ def sweep(
153155
eta_reversed.append(eta_j)
154156
first = contract(eqs.first, *sites[0], S)
155157
eta = [first.reshape(1, *first.shape[depth:]), *reversed(eta_reversed)]
156-
logger.debug("Right-to-left sweep", seconds=(perf_counter_ns() - tms) * 1e-9)
158+
logger.debug("Right-to-left sweep: %.3f s", (perf_counter_ns() - tms) * 1e-9)
157159

158160
return [to_numpy(site) for site in unpad(eta, kind)]

0 commit comments

Comments
 (0)