Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ tensor, so there is no wrapper type.
- Backend-agnostic code: go through `src_method.utils._backend` instead of
importing `numpy` or `cupy` directly in the algorithms, so CPU and GPU paths
stay in sync.
- Log with `structlog` via `src_method.utils.logging_config`, never `print`.
- Log with the standard library: `logger = logging.getLogger(__name__)` per
module, lazy `%`-style arguments, never `print`. Never add handlers, levels
or `basicConfig` in the package; progress messages go at `DEBUG`.
- **Run `uv run prek run --all-files` and the relevant tests before
pushing, and fix every finding.** `lint.yml` runs the same hooks in CI, so a
skipped lint is a red PR.
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ The result then pairs with a ket by plain contraction, with no further conjugati

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

### Logging

`src_method` logs through the standard library `logging` module, under the
`src_method` logger, and stays silent by default: it adds only a `NullHandler`
and never touches the root logger or its handlers. Progress and timing messages
are emitted at `DEBUG`, the small-network fallback at `WARNING`. To see them,
configure logging in your application:

```python
import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger("src_method").setLevel(logging.DEBUG)
```

## Installation

```bash
Expand Down
24 changes: 14 additions & 10 deletions benches/primitives/leonardo/bench_mpo_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

from __future__ import annotations

import logging
from time import perf_counter_ns

import cyclopts
import numpy as np
import quimb.tensor as qtn
import structlog

from src_method import apply

# Set up logger
logger = structlog.get_logger()
logger = logging.getLogger(__name__)


# Initialize cyclopts app
Expand All @@ -38,13 +37,13 @@ def main(
array_type = np.complex128

logger.info(
"benchmark_start",
n_sites=n_sites,
chi_out=chi_out,
phys_dim=phys_dim,
dtype="complex128",
run=run,
compare=compare,
"benchmark_start: n_sites=%d, chi_out=%d, phys_dim=%d, dtype=complex128, "
"run=%s, compare=%s",
n_sites,
chi_out,
phys_dim,
run,
compare,
)

# Generate a random MPO and a perturbed identity MPO
Expand Down Expand Up @@ -92,4 +91,9 @@ def main(


if __name__ == "__main__":
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
# Report the per-sweep timings of src_method too.
logging.getLogger("src_method").setLevel(logging.DEBUG)
app()
3 changes: 0 additions & 3 deletions benches/primitives/leonardo/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
#SBATCH --exclusive
#SBATCH --output=logs/1000_%j.out

# Python application's loggin level
export LOG_LEVEL_SRC=DEBUG

# Automatic OpenMP binding
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
export OMP_PLACES=cores
Expand Down
3 changes: 1 addition & 2 deletions benches/stack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ uv run python benches/stack/bench_depth.py accuracy --output accuracy.md
uv run python benches/stack/bench_depth.py timing --output timing.md
```

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

## Results

Expand Down
7 changes: 4 additions & 3 deletions benches/stack/bench_depth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@

from __future__ import annotations

import logging
from dataclasses import astuple, dataclass, fields
from functools import reduce
from pathlib import Path
from time import perf_counter

import cyclopts
import numpy as np
import structlog
from scipy.linalg import expm

from src_method import apply, src

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

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


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(message)s")
app()
15 changes: 15 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,18 @@ container.
[Visual Studio Code]: https://code.visualstudio.com/
[GitHub CLI]: https://cli.github.com/
[at this link]: https://docs.algorithmiq.fi/src_method

### Logging

`src_method` logs through the standard library `logging` module, under the
`src_method` logger, and stays silent by default: it adds only a `NullHandler`
and never touches the root logger or its handlers. Progress and timing messages
are emitted at `DEBUG`, the small-network fallback at `WARNING`. To see them,
configure logging in your application:

```python
import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger("src_method").setLevel(logging.DEBUG)
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ classifiers = [
"Typing :: Typed",
]
dynamic = ["version"]
dependencies = ["numpy>=2.4.4,<3", "opt_einsum>=3.4.0", "structlog>=25.5.0"]
dependencies = ["numpy>=2.4.4,<3", "opt_einsum>=3.4.0"]


[project.optional-dependencies]
Expand Down
5 changes: 5 additions & 0 deletions src/src_method/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@

from __future__ import annotations

import logging

from ._version import version as __version__
from ._version import version_tuple as __version_tuple__
from .apply import apply
from .compress import compress
from .stack import src

# Stay silent unless the application configures logging.
logging.getLogger(__name__).addHandler(logging.NullHandler())

__all__ = ["__version__", "__version_tuple__", "apply", "compress", "src"]
20 changes: 11 additions & 9 deletions src/src_method/_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@

from __future__ import annotations

import logging
from functools import cache
from itertools import count
from math import prod
from time import perf_counter_ns
from typing import TYPE_CHECKING, NamedTuple

import structlog
from opt_einsum import contract_expression, get_symbol

from ._tensor_train import pad, unpad
Expand All @@ -35,7 +35,7 @@

from ._tensor_train import TrainKind

logger = structlog.get_logger(__name__)
logger = logging.getLogger(__name__)


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

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

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

return [to_numpy(site) for site in unpad(eta, kind)]
22 changes: 10 additions & 12 deletions src/src_method/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

import structlog

from ._sweep import sweep
from ._tensor_train import (
MIN_SRC_SITES,
Expand All @@ -19,15 +18,14 @@
normalize_stack,
)
from ._validation import validate_chi_out, validate_cutoff
from .utils import default_rng, get_xp, setup_logging, sketch_dtype
from .utils import default_rng, get_xp, sketch_dtype

if TYPE_CHECKING:
from collections.abc import Sequence

from numpy.typing import DTypeLike, NDArray

setup_logging()
logger = structlog.get_logger(__name__)
logger = logging.getLogger(__name__)

LOG_WARN_SMALL = (
"The current SRC implementation targets tensor networks with 3 or more sites. "
Expand Down Expand Up @@ -109,12 +107,12 @@ def src(
logger.warning(LOG_WARN_SMALL)
return exact_stack(layers, chi_out, kind)

logger.info(
"Starting SRC",
n_sites=n_sites,
depth=len(layers),
output=kind,
device=xp.__name__,
logger.debug(
"Starting SRC: n_sites=%d, depth=%d, output=%s, device=%s",
n_sites,
len(layers),
kind,
xp.__name__,
)
result = sweep(
layers,
Expand All @@ -125,5 +123,5 @@ def src(
cutoff=cutoff,
dtype=sketch_dtype(dtype, *layers),
)
logger.info("SRC complete.")
logger.debug("SRC complete")
return result
2 changes: 0 additions & 2 deletions src/src_method/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@

from ._backend import default_rng, gaussian_sketch, get_xp, sketch_dtype, to_numpy
from .linalg import truncated_qr
from .logging_config import setup_logging

__all__ = [
"default_rng",
"gaussian_sketch",
"get_xp",
"setup_logging",
"sketch_dtype",
"to_numpy",
"truncated_qr",
Expand Down
50 changes: 0 additions & 50 deletions src/src_method/utils/logging_config.py

This file was deleted.

Loading
Loading