usel 1.0.4__py3-none-any.whl
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.
- usel/__init__.py +22 -0
- usel/cli/__init__.py +7 -0
- usel/cli/main.py +151 -0
- usel/config/__init__.py +7 -0
- usel/config/loader.py +70 -0
- usel/exceptions/__init__.py +45 -0
- usel/io/__init__.py +17 -0
- usel/io/readers.py +55 -0
- usel/io/writers.py +47 -0
- usel/linalg/__init__.py +14 -0
- usel/linalg/decompositions.py +42 -0
- usel/logging/__init__.py +12 -0
- usel/logging/logger.py +74 -0
- usel/math/__init__.py +8 -0
- usel/math/matrix.py +212 -0
- usel/math/transforms.py +48 -0
- usel/random/__init__.py +19 -0
- usel/random/generators.py +44 -0
- usel/schrodinger/__init__.py +60 -0
- usel/schrodinger/boundary_conditions.py +75 -0
- usel/schrodinger/evolution.py +61 -0
- usel/schrodinger/grids.py +85 -0
- usel/schrodinger/hamiltonian.py +74 -0
- usel/schrodinger/normalization.py +65 -0
- usel/schrodinger/observables.py +86 -0
- usel/schrodinger/operators.py +111 -0
- usel/schrodinger/potentials.py +67 -0
- usel/schrodinger/propagators.py +25 -0
- usel/schrodinger/results.py +67 -0
- usel/schrodinger/solver.py +325 -0
- usel/schrodinger/validation.py +33 -0
- usel/schrodinger/visualization.py +54 -0
- usel/schrodinger/wavefunctions.py +96 -0
- usel/solvers/__init__.py +22 -0
- usel/solvers/crank_nicolson.py +33 -0
- usel/solvers/finite_difference.py +29 -0
- usel/solvers/imaginary_time.py +24 -0
- usel/solvers/integration.py +64 -0
- usel/solvers/ode.py +84 -0
- usel/solvers/rk4.py +31 -0
- usel/solvers/root_finding.py +132 -0
- usel/solvers/split_operator.py +39 -0
- usel/utils/__init__.py +26 -0
- usel/utils/precision.py +24 -0
- usel/utils/profiling.py +56 -0
- usel/utils/timing.py +68 -0
- usel/utils/validation.py +31 -0
- usel-1.0.4.dist-info/METADATA +157 -0
- usel-1.0.4.dist-info/RECORD +53 -0
- usel-1.0.4.dist-info/WHEEL +5 -0
- usel-1.0.4.dist-info/entry_points.txt +2 -0
- usel-1.0.4.dist-info/licenses/LICENSE +21 -0
- usel-1.0.4.dist-info/top_level.txt +1 -0
usel/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""usel: an open-source, CPU-only scientific computing framework.
|
|
2
|
+
|
|
3
|
+
This package provides the mathematical and computational foundation for
|
|
4
|
+
quantum mechanics, relativistic quantum mechanics, and computational
|
|
5
|
+
electromagnetics:
|
|
6
|
+
|
|
7
|
+
- Reusable matrix and linear algebra utilities
|
|
8
|
+
- Numerical solvers (root finding, ODEs, integration)
|
|
9
|
+
- FFT based frequency analysis
|
|
10
|
+
- Schrödinger equation solvers (bound states, wave-packet propagation)
|
|
11
|
+
- Dirac equation solvers (relativistic spinors, Pauli/Dirac matrices)
|
|
12
|
+
- Maxwell equation solvers (2D FDTD, electrostatics)
|
|
13
|
+
- Random number and matrix generation
|
|
14
|
+
- Configuration, IO, logging, and validation utilities
|
|
15
|
+
- A command line interface (``usel``)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
__version__ = "1.1.0"
|
|
21
|
+
__license__ = "MIT"
|
|
22
|
+
__all__ = ["__version__", "__license__"]
|
usel/cli/__init__.py
ADDED
usel/cli/main.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""usel command line interface.
|
|
2
|
+
|
|
3
|
+
Commands
|
|
4
|
+
--------
|
|
5
|
+
usel version Print the installed usel version.
|
|
6
|
+
usel doctor Check environment health (Python version, dependencies).
|
|
7
|
+
usel benchmark Run a quick matrix-multiplication benchmark.
|
|
8
|
+
usel info Print platform and package information.
|
|
9
|
+
usel config Show or validate a configuration file.
|
|
10
|
+
usel test Run the test suite via pytest.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import importlib
|
|
17
|
+
import platform
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from collections.abc import Sequence
|
|
22
|
+
|
|
23
|
+
import usel
|
|
24
|
+
from usel.math.matrix import Matrix
|
|
25
|
+
|
|
26
|
+
_REQUIRED_PACKAGES = ("numpy", "scipy", "sympy")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _cmd_version(_: argparse.Namespace) -> int:
|
|
30
|
+
print(f"usel version {usel.__version__}")
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _cmd_info(_: argparse.Namespace) -> int:
|
|
35
|
+
print("usel Info")
|
|
36
|
+
print("-" * 40)
|
|
37
|
+
print(f"Version: {usel.__version__}")
|
|
38
|
+
print(f"License: {usel.__license__}")
|
|
39
|
+
print(f"Python: {platform.python_version()}")
|
|
40
|
+
print(f"Platform: {platform.platform()}")
|
|
41
|
+
print(f"Processor: {platform.processor() or 'unknown'}")
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cmd_doctor(_: argparse.Namespace) -> int:
|
|
46
|
+
print("Running usel environment check...")
|
|
47
|
+
ok = True
|
|
48
|
+
|
|
49
|
+
major, minor = sys.version_info[:2]
|
|
50
|
+
if (major, minor) >= (3, 11):
|
|
51
|
+
print(f"[OK] Python version {major}.{minor} >= 3.11")
|
|
52
|
+
else:
|
|
53
|
+
print(f"[FAIL] Python version {major}.{minor} < 3.11")
|
|
54
|
+
ok = False
|
|
55
|
+
|
|
56
|
+
for package in _REQUIRED_PACKAGES:
|
|
57
|
+
try:
|
|
58
|
+
importlib.import_module(package)
|
|
59
|
+
print(f"[OK] Dependency '{package}' is importable")
|
|
60
|
+
except ImportError:
|
|
61
|
+
print(f"[FAIL] Dependency '{package}' is missing")
|
|
62
|
+
ok = False
|
|
63
|
+
|
|
64
|
+
if ok:
|
|
65
|
+
print("\nAll checks passed. usel is ready to use.")
|
|
66
|
+
else:
|
|
67
|
+
print("\nSome checks failed. Please review the output above.")
|
|
68
|
+
return 0 if ok else 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _cmd_benchmark(args: argparse.Namespace) -> int:
|
|
72
|
+
size = args.size
|
|
73
|
+
print(f"Benchmarking {size}x{size} matrix multiplication...")
|
|
74
|
+
a = Matrix.random(size, size, seed=42)
|
|
75
|
+
b = Matrix.random(size, size, seed=43)
|
|
76
|
+
|
|
77
|
+
start = time.perf_counter()
|
|
78
|
+
_ = a @ b
|
|
79
|
+
elapsed = time.perf_counter() - start
|
|
80
|
+
|
|
81
|
+
print(f"Completed in {elapsed:.6f} seconds")
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _cmd_config(args: argparse.Namespace) -> int:
|
|
86
|
+
from usel.config.loader import load_config
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
data = load_config(args.path)
|
|
90
|
+
except Exception as exc: # noqa: BLE001
|
|
91
|
+
print(f"[FAIL] Could not load configuration: {exc}")
|
|
92
|
+
return 1
|
|
93
|
+
|
|
94
|
+
print(f"[OK] Configuration file '{args.path}' loaded successfully")
|
|
95
|
+
print(f"Keys: {sorted(data.keys())}")
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _cmd_test(args: argparse.Namespace) -> int:
|
|
100
|
+
cmd = [sys.executable, "-m", "pytest"]
|
|
101
|
+
if args.path:
|
|
102
|
+
cmd.append(args.path)
|
|
103
|
+
result = subprocess.run(cmd, check=False) # noqa: S603
|
|
104
|
+
return result.returncode
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
108
|
+
"""Build and return the usel argument parser."""
|
|
109
|
+
parser = argparse.ArgumentParser(
|
|
110
|
+
prog="usel", description="usel: CPU-only scientific computing framework"
|
|
111
|
+
)
|
|
112
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
113
|
+
|
|
114
|
+
subparsers.add_parser("version", help="Print the installed usel version").set_defaults(
|
|
115
|
+
func=_cmd_version
|
|
116
|
+
)
|
|
117
|
+
subparsers.add_parser("info", help="Print platform and package information").set_defaults(
|
|
118
|
+
func=_cmd_info
|
|
119
|
+
)
|
|
120
|
+
subparsers.add_parser(
|
|
121
|
+
"doctor", help="Check environment health (Python version, dependencies)"
|
|
122
|
+
).set_defaults(func=_cmd_doctor)
|
|
123
|
+
|
|
124
|
+
benchmark_parser = subparsers.add_parser(
|
|
125
|
+
"benchmark", help="Run a quick matrix-multiplication benchmark"
|
|
126
|
+
)
|
|
127
|
+
benchmark_parser.add_argument(
|
|
128
|
+
"--size", type=int, default=200, help="Matrix dimension for the benchmark (default: 200)"
|
|
129
|
+
)
|
|
130
|
+
benchmark_parser.set_defaults(func=_cmd_benchmark)
|
|
131
|
+
|
|
132
|
+
config_parser = subparsers.add_parser("config", help="Load and validate a configuration file")
|
|
133
|
+
config_parser.add_argument("path", help="Path to a .yaml, .json, or .toml configuration file")
|
|
134
|
+
config_parser.set_defaults(func=_cmd_config)
|
|
135
|
+
|
|
136
|
+
test_parser = subparsers.add_parser("test", help="Run the test suite via pytest")
|
|
137
|
+
test_parser.add_argument("path", nargs="?", default=None, help="Optional test path filter")
|
|
138
|
+
test_parser.set_defaults(func=_cmd_test)
|
|
139
|
+
|
|
140
|
+
return parser
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
144
|
+
"""usel CLI entry point."""
|
|
145
|
+
parser = build_parser()
|
|
146
|
+
args = parser.parse_args(argv)
|
|
147
|
+
return int(args.func(args))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
raise SystemExit(main())
|
usel/config/__init__.py
ADDED
usel/config/loader.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Load and save configuration files in YAML, JSON, or TOML format.
|
|
2
|
+
|
|
3
|
+
The format is inferred from the file extension: ``.yaml``/``.yml``,
|
|
4
|
+
``.json``, or ``.toml``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from usel.exceptions import ConfigError
|
|
14
|
+
|
|
15
|
+
_SUPPORTED_SUFFIXES = {".yaml", ".yml", ".json", ".toml"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_config(filepath: str | Path) -> dict[str, Any]:
|
|
19
|
+
"""Load a configuration file, inferring the format from its extension."""
|
|
20
|
+
path = Path(filepath)
|
|
21
|
+
if not path.exists():
|
|
22
|
+
raise ConfigError(f"Configuration file not found: {path}")
|
|
23
|
+
if path.suffix not in _SUPPORTED_SUFFIXES:
|
|
24
|
+
raise ConfigError(
|
|
25
|
+
f"Unsupported configuration format '{path.suffix}'. "
|
|
26
|
+
f"Supported formats: {sorted(_SUPPORTED_SUFFIXES)}"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
text = path.read_text(encoding="utf-8")
|
|
31
|
+
if path.suffix in (".yaml", ".yml"):
|
|
32
|
+
import yaml
|
|
33
|
+
|
|
34
|
+
data = yaml.safe_load(text) or {}
|
|
35
|
+
elif path.suffix == ".json":
|
|
36
|
+
data = json.loads(text) if text.strip() else {}
|
|
37
|
+
else: # .toml
|
|
38
|
+
import toml
|
|
39
|
+
|
|
40
|
+
data = toml.loads(text)
|
|
41
|
+
except Exception as exc: # noqa: BLE001 - re-raised as ConfigError
|
|
42
|
+
raise ConfigError(f"Failed to parse configuration file {path}: {exc}") from exc
|
|
43
|
+
|
|
44
|
+
if not isinstance(data, dict):
|
|
45
|
+
raise ConfigError(f"Configuration root must be a mapping/object, got {type(data).__name__}")
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def save_config(data: dict[str, Any], filepath: str | Path) -> None:
|
|
50
|
+
"""Save a configuration dictionary, inferring the format from its extension."""
|
|
51
|
+
path = Path(filepath)
|
|
52
|
+
if path.suffix not in _SUPPORTED_SUFFIXES:
|
|
53
|
+
raise ConfigError(
|
|
54
|
+
f"Unsupported configuration format '{path.suffix}'. "
|
|
55
|
+
f"Supported formats: {sorted(_SUPPORTED_SUFFIXES)}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
if path.suffix in (".yaml", ".yml"):
|
|
60
|
+
import yaml
|
|
61
|
+
|
|
62
|
+
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
|
|
63
|
+
elif path.suffix == ".json":
|
|
64
|
+
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
65
|
+
else: # .toml
|
|
66
|
+
import toml
|
|
67
|
+
|
|
68
|
+
path.write_text(toml.dumps(data), encoding="utf-8")
|
|
69
|
+
except Exception as exc: # noqa: BLE001 - re-raised as ConfigError
|
|
70
|
+
raise ConfigError(f"Failed to write configuration file {path}: {exc}") from exc
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Custom exception hierarchy for usel."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class uselError(Exception):
|
|
7
|
+
"""Base exception for all usel errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MatrixError(uselError):
|
|
11
|
+
"""Raised for invalid matrix operations (shape mismatch, singularity, etc.)."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SolverError(uselError):
|
|
15
|
+
"""Raised when a numerical solver fails to converge or receives bad input."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConfigError(uselError):
|
|
19
|
+
"""Raised for configuration loading, parsing, or validation failures."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ValidationError(uselError):
|
|
23
|
+
"""Raised when input validation fails."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IOError_(uselError):
|
|
27
|
+
"""Raised for file input/output failures.
|
|
28
|
+
|
|
29
|
+
Named ``IOError_`` to avoid shadowing the Python builtin ``IOError``.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PhysicsError(uselError):
|
|
34
|
+
"""Raised when a physics simulation encounters invalid parameters or diverges."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"uselError",
|
|
39
|
+
"MatrixError",
|
|
40
|
+
"SolverError",
|
|
41
|
+
"ConfigError",
|
|
42
|
+
"ValidationError",
|
|
43
|
+
"IOError_",
|
|
44
|
+
"PhysicsError",
|
|
45
|
+
]
|
usel/io/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""File input/output utilities: CSV, JSON, NumPy binary, and plain text."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from usel.io.readers import read_csv, read_json, read_npy, read_text
|
|
6
|
+
from usel.io.writers import write_csv, write_json, write_npy, write_text
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"read_csv",
|
|
10
|
+
"read_json",
|
|
11
|
+
"read_npy",
|
|
12
|
+
"read_text",
|
|
13
|
+
"write_csv",
|
|
14
|
+
"write_json",
|
|
15
|
+
"write_npy",
|
|
16
|
+
"write_text",
|
|
17
|
+
]
|
usel/io/readers.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Readers for CSV, JSON, NumPy binary (.npy/.npz), and plain text files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from usel.exceptions import IOError_
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def read_csv(filepath: str | Path, delimiter: str = ",", skip_header: int = 0) -> np.ndarray:
|
|
15
|
+
"""Read a CSV file into a NumPy array."""
|
|
16
|
+
path = Path(filepath)
|
|
17
|
+
if not path.exists():
|
|
18
|
+
raise IOError_(f"CSV file not found: {path}")
|
|
19
|
+
try:
|
|
20
|
+
return np.genfromtxt(path, delimiter=delimiter, skip_header=skip_header, dtype=float)
|
|
21
|
+
except Exception as exc: # noqa: BLE001
|
|
22
|
+
raise IOError_(f"Failed to read CSV file {path}: {exc}") from exc
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_json(filepath: str | Path) -> Any:
|
|
26
|
+
"""Read a JSON file and return the parsed object."""
|
|
27
|
+
path = Path(filepath)
|
|
28
|
+
if not path.exists():
|
|
29
|
+
raise IOError_(f"JSON file not found: {path}")
|
|
30
|
+
try:
|
|
31
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
32
|
+
except Exception as exc: # noqa: BLE001
|
|
33
|
+
raise IOError_(f"Failed to read JSON file {path}: {exc}") from exc
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_npy(filepath: str | Path) -> np.ndarray:
|
|
37
|
+
"""Read a NumPy binary (.npy) file into an array."""
|
|
38
|
+
path = Path(filepath)
|
|
39
|
+
if not path.exists():
|
|
40
|
+
raise IOError_(f"NumPy binary file not found: {path}")
|
|
41
|
+
try:
|
|
42
|
+
return np.load(path, allow_pickle=False)
|
|
43
|
+
except Exception as exc: # noqa: BLE001
|
|
44
|
+
raise IOError_(f"Failed to read .npy file {path}: {exc}") from exc
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_text(filepath: str | Path, encoding: str = "utf-8") -> str:
|
|
48
|
+
"""Read a plain text file and return its contents as a string."""
|
|
49
|
+
path = Path(filepath)
|
|
50
|
+
if not path.exists():
|
|
51
|
+
raise IOError_(f"Text file not found: {path}")
|
|
52
|
+
try:
|
|
53
|
+
return path.read_text(encoding=encoding)
|
|
54
|
+
except Exception as exc: # noqa: BLE001
|
|
55
|
+
raise IOError_(f"Failed to read text file {path}: {exc}") from exc
|
usel/io/writers.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Writers for CSV, JSON, NumPy binary (.npy), and plain text files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from usel.exceptions import IOError_
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def write_csv(data: np.ndarray, filepath: str | Path, delimiter: str = ",") -> None:
|
|
15
|
+
"""Write a NumPy array to a CSV file."""
|
|
16
|
+
path = Path(filepath)
|
|
17
|
+
try:
|
|
18
|
+
np.savetxt(path, np.asarray(data), delimiter=delimiter)
|
|
19
|
+
except Exception as exc: # noqa: BLE001
|
|
20
|
+
raise IOError_(f"Failed to write CSV file {path}: {exc}") from exc
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def write_json(data: Any, filepath: str | Path, indent: int = 2) -> None:
|
|
24
|
+
"""Write a JSON-serializable object to a file."""
|
|
25
|
+
path = Path(filepath)
|
|
26
|
+
try:
|
|
27
|
+
path.write_text(json.dumps(data, indent=indent), encoding="utf-8")
|
|
28
|
+
except Exception as exc: # noqa: BLE001
|
|
29
|
+
raise IOError_(f"Failed to write JSON file {path}: {exc}") from exc
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_npy(data: np.ndarray, filepath: str | Path) -> None:
|
|
33
|
+
"""Write a NumPy array to a .npy binary file."""
|
|
34
|
+
path = Path(filepath)
|
|
35
|
+
try:
|
|
36
|
+
np.save(path, np.asarray(data))
|
|
37
|
+
except Exception as exc: # noqa: BLE001
|
|
38
|
+
raise IOError_(f"Failed to write .npy file {path}: {exc}") from exc
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def write_text(content: str, filepath: str | Path, encoding: str = "utf-8") -> None:
|
|
42
|
+
"""Write a string to a plain text file."""
|
|
43
|
+
path = Path(filepath)
|
|
44
|
+
try:
|
|
45
|
+
path.write_text(content, encoding=encoding)
|
|
46
|
+
except Exception as exc: # noqa: BLE001
|
|
47
|
+
raise IOError_(f"Failed to write text file {path}: {exc}") from exc
|
usel/linalg/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Linear algebra utilities: eigen decompositions, SVD, QR, LU, Cholesky."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from usel.linalg.decompositions import (
|
|
6
|
+
cholesky,
|
|
7
|
+
eigenvalues,
|
|
8
|
+
eigenvectors,
|
|
9
|
+
lu,
|
|
10
|
+
qr,
|
|
11
|
+
svd,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = ["eigenvalues", "eigenvectors", "svd", "qr", "lu", "cholesky"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Functional wrappers around common matrix decompositions.
|
|
2
|
+
|
|
3
|
+
These functions accept and return :class:`usel.math.Matrix` instances
|
|
4
|
+
(or raw NumPy arrays where noted), providing a functional API alongside the
|
|
5
|
+
object-oriented methods already available on ``Matrix``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from usel.math.matrix import Matrix
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def eigenvalues(matrix: Matrix) -> np.ndarray:
|
|
16
|
+
"""Return the eigenvalues of a square matrix."""
|
|
17
|
+
return matrix.eigenvalues()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def eigenvectors(matrix: Matrix) -> tuple[np.ndarray, Matrix]:
|
|
21
|
+
"""Return (eigenvalues, eigenvectors) of a square matrix."""
|
|
22
|
+
return matrix.eigenvectors()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def svd(matrix: Matrix) -> tuple[Matrix, np.ndarray, Matrix]:
|
|
26
|
+
"""Singular value decomposition. Returns (U, singular_values, Vt)."""
|
|
27
|
+
return matrix.decompose_svd()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def qr(matrix: Matrix) -> tuple[Matrix, Matrix]:
|
|
31
|
+
"""QR decomposition. Returns (Q, R)."""
|
|
32
|
+
return matrix.decompose_qr()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def lu(matrix: Matrix) -> tuple[Matrix, Matrix, Matrix]:
|
|
36
|
+
"""LU decomposition with partial pivoting. Returns (P, L, U)."""
|
|
37
|
+
return matrix.decompose_lu()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def cholesky(matrix: Matrix) -> Matrix:
|
|
41
|
+
"""Cholesky decomposition for symmetric positive-definite matrices."""
|
|
42
|
+
return matrix.decompose_cholesky()
|
usel/logging/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Logging utilities: console logger, file logger, debug mode, performance logger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from usel.logging.logger import (
|
|
6
|
+
PerformanceLogger,
|
|
7
|
+
get_console_logger,
|
|
8
|
+
get_file_logger,
|
|
9
|
+
set_debug_mode,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = ["get_console_logger", "get_file_logger", "set_debug_mode", "PerformanceLogger"]
|
usel/logging/logger.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Console and file loggers, debug mode toggling, and a performance logger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_DEFAULT_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_console_logger(name: str = "usel", level: int = logging.INFO) -> logging.Logger:
|
|
13
|
+
"""Return a logger configured to write formatted messages to the console."""
|
|
14
|
+
logger = logging.getLogger(name)
|
|
15
|
+
logger.setLevel(level)
|
|
16
|
+
if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
|
|
17
|
+
handler = logging.StreamHandler()
|
|
18
|
+
handler.setFormatter(logging.Formatter(_DEFAULT_FORMAT))
|
|
19
|
+
logger.addHandler(handler)
|
|
20
|
+
return logger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_file_logger(
|
|
24
|
+
name: str = "usel", filepath: str | Path = "usel.log", level: int = logging.INFO
|
|
25
|
+
) -> logging.Logger:
|
|
26
|
+
"""Return a logger configured to write formatted messages to a file."""
|
|
27
|
+
logger = logging.getLogger(name)
|
|
28
|
+
logger.setLevel(level)
|
|
29
|
+
path = Path(filepath)
|
|
30
|
+
if not any(
|
|
31
|
+
isinstance(h, logging.FileHandler) and h.baseFilename == str(path.resolve())
|
|
32
|
+
for h in logger.handlers
|
|
33
|
+
):
|
|
34
|
+
handler = logging.FileHandler(path)
|
|
35
|
+
handler.setFormatter(logging.Formatter(_DEFAULT_FORMAT))
|
|
36
|
+
logger.addHandler(handler)
|
|
37
|
+
return logger
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def set_debug_mode(logger: logging.Logger, enabled: bool = True) -> None:
|
|
41
|
+
"""Toggle debug-level verbosity on the given logger."""
|
|
42
|
+
logger.setLevel(logging.DEBUG if enabled else logging.INFO)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PerformanceLogger:
|
|
46
|
+
"""A simple named-checkpoint performance logger.
|
|
47
|
+
|
|
48
|
+
Examples
|
|
49
|
+
--------
|
|
50
|
+
>>> perf = PerformanceLogger()
|
|
51
|
+
>>> perf.checkpoint("start")
|
|
52
|
+
>>> perf.checkpoint("end")
|
|
53
|
+
>>> perf.report()['start'] >= 0
|
|
54
|
+
True
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
self._checkpoints: dict[str, float] = {}
|
|
59
|
+
self._start = time.perf_counter()
|
|
60
|
+
|
|
61
|
+
def checkpoint(self, label: str) -> float:
|
|
62
|
+
"""Record a checkpoint and return elapsed seconds since construction."""
|
|
63
|
+
elapsed = time.perf_counter() - self._start
|
|
64
|
+
self._checkpoints[label] = elapsed
|
|
65
|
+
return elapsed
|
|
66
|
+
|
|
67
|
+
def report(self) -> dict[str, float]:
|
|
68
|
+
"""Return a mapping of checkpoint labels to elapsed seconds."""
|
|
69
|
+
return dict(self._checkpoints)
|
|
70
|
+
|
|
71
|
+
def reset(self) -> None:
|
|
72
|
+
"""Reset the performance logger's start time and checkpoints."""
|
|
73
|
+
self._checkpoints.clear()
|
|
74
|
+
self._start = time.perf_counter()
|
usel/math/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Reusable numerical utilities: matrix creation, algebra, norms, transforms."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from usel.math.matrix import Matrix
|
|
6
|
+
from usel.math.transforms import fft, frequency_analysis, ifft
|
|
7
|
+
|
|
8
|
+
__all__ = ["Matrix", "fft", "ifft", "frequency_analysis"]
|