sdos 0.1.0__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.
Files changed (58) hide show
  1. sdos-0.1.0/LICENSE +29 -0
  2. sdos-0.1.0/PKG-INFO +116 -0
  3. sdos-0.1.0/README.md +90 -0
  4. sdos-0.1.0/pyproject.toml +37 -0
  5. sdos-0.1.0/setup.cfg +4 -0
  6. sdos-0.1.0/src/sdos/__init__.py +34 -0
  7. sdos-0.1.0/src/sdos/__main__.py +3 -0
  8. sdos-0.1.0/src/sdos/api.py +80 -0
  9. sdos-0.1.0/src/sdos/bounds.py +67 -0
  10. sdos-0.1.0/src/sdos/cli.py +186 -0
  11. sdos-0.1.0/src/sdos/dos.py +352 -0
  12. sdos-0.1.0/src/sdos/eigwin.py +279 -0
  13. sdos-0.1.0/src/sdos/exact.py +58 -0
  14. sdos-0.1.0/src/sdos/generalized.py +284 -0
  15. sdos-0.1.0/src/sdos/ic.py +82 -0
  16. sdos-0.1.0/src/sdos/interfaces/__init__.py +5 -0
  17. sdos-0.1.0/src/sdos/interfaces/_assemble.py +42 -0
  18. sdos-0.1.0/src/sdos/interfaces/kwon_si.py +165 -0
  19. sdos-0.1.0/src/sdos/interfaces/lmp.py +97 -0
  20. sdos-0.1.0/src/sdos/interfaces/sisl_if.py +34 -0
  21. sdos-0.1.0/src/sdos/interfaces/skf.py +253 -0
  22. sdos-0.1.0/src/sdos/interfaces/xu_carbon.py +82 -0
  23. sdos-0.1.0/src/sdos/io.py +120 -0
  24. sdos-0.1.0/src/sdos/maxent.py +231 -0
  25. sdos-0.1.0/src/sdos/moments.py +147 -0
  26. sdos-0.1.0/src/sdos/operators.py +95 -0
  27. sdos-0.1.0/src/sdos/phonon.py +58 -0
  28. sdos-0.1.0/src/sdos/projections.py +188 -0
  29. sdos-0.1.0/src/sdos/reconstruct.py +72 -0
  30. sdos-0.1.0/src/sdos/results.py +176 -0
  31. sdos-0.1.0/src/sdos/thermo.py +85 -0
  32. sdos-0.1.0/src/sdos/windows.py +125 -0
  33. sdos-0.1.0/src/sdos.egg-info/PKG-INFO +116 -0
  34. sdos-0.1.0/src/sdos.egg-info/SOURCES.txt +56 -0
  35. sdos-0.1.0/src/sdos.egg-info/dependency_links.txt +1 -0
  36. sdos-0.1.0/src/sdos.egg-info/entry_points.txt +2 -0
  37. sdos-0.1.0/src/sdos.egg-info/requires.txt +14 -0
  38. sdos-0.1.0/src/sdos.egg-info/top_level.txt +1 -0
  39. sdos-0.1.0/tests/test_exact.py +73 -0
  40. sdos-0.1.0/tests/test_exact_and_api.py +133 -0
  41. sdos-0.1.0/tests/test_examples.py +16 -0
  42. sdos-0.1.0/tests/test_fullerene.py +106 -0
  43. sdos-0.1.0/tests/test_generalized.py +130 -0
  44. sdos-0.1.0/tests/test_ic.py +62 -0
  45. sdos-0.1.0/tests/test_impartial.py +57 -0
  46. sdos-0.1.0/tests/test_interfaces.py +85 -0
  47. sdos-0.1.0/tests/test_io.py +35 -0
  48. sdos-0.1.0/tests/test_kwon_si.py +59 -0
  49. sdos-0.1.0/tests/test_maxent_bootstrap.py +39 -0
  50. sdos-0.1.0/tests/test_maxent_robust.py +52 -0
  51. sdos-0.1.0/tests/test_phonon.py +69 -0
  52. sdos-0.1.0/tests/test_projections.py +86 -0
  53. sdos-0.1.0/tests/test_regression.py +90 -0
  54. sdos-0.1.0/tests/test_results.py +67 -0
  55. sdos-0.1.0/tests/test_skf.py +183 -0
  56. sdos-0.1.0/tests/test_thermo.py +98 -0
  57. sdos-0.1.0/tests/test_window.py +113 -0
  58. sdos-0.1.0/tests/test_xu_carbon.py +85 -0
sdos-0.1.0/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, David A. Drabold
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sdos-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: sdos
3
+ Version: 0.1.0
4
+ Summary: Order-N spectral density of states (KPM, Maxent, shift-invert) for large sparse Hermitian problems
5
+ Author-email: "David A. Drabold" <drabold@ohio.edu>
6
+ License-Expression: BSD-3-Clause
7
+ Keywords: density of states,kernel polynomial method,maximum entropy,sparse eigenvalue,tight binding,vibrational spectra
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering :: Physics
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy>=1.22
16
+ Requires-Dist: scipy>=1.8
17
+ Provides-Extra: plot
18
+ Requires-Dist: matplotlib; extra == "plot"
19
+ Provides-Extra: amg
20
+ Requires-Dist: pyamg; extra == "amg"
21
+ Provides-Extra: sisl
22
+ Requires-Dist: sisl; extra == "sisl"
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest; extra == "test"
25
+ Dynamic: license-file
26
+
27
+ # sdos
28
+
29
+ The density of states of a big sparse matrix, in one call. Order-N stochastic trace estimation with the kernel polynomial
30
+ method (KPM) or maximum entropy; real symmetric or complex Hermitian; explicit or matrix-free; standard `A x = λ x` or
31
+ generalized `A x = λ B x`; electrons or phonons.
32
+
33
+ Status: **0.1.0, first public release.** The method, its tests against dense diagonalization and an independent code, benchmarks up to
34
+ millions of atoms, and the limits of the approach (what does not scale) are described in the report in `paper/` (REVTeX source and bibliography).
35
+ Docs: `docs/guide.md`, `docs/theory.md`. Tests: `pip install -e ".[test]" && pytest`.
36
+
37
+ **In short.** `sdos` estimates the density of states of a large sparse Hermitian matrix (or a generalized problem with an overlap or mass matrix)
38
+ from Chebyshev moments computed with random vectors, so the cost grows linearly with the matrix size. The density is reconstructed by the kernel
39
+ polynomial method or by maximum entropy, and the same moments give band energies, Fermi levels, local and projected densities and harmonic
40
+ thermodynamics. Interfaces: tight-binding and DFTB Hamiltonians, SIESTA through sisl, and matrix-free LAMMPS Hessians for phonons.
41
+ The underlying methods are due to Silver, Röder, Kress, Voter, Drabold, Sankey and collaborators; the report states what is added.
42
+
43
+ ## Install
44
+
45
+ pip install -e ".[test]" # from a clone; add [plot] for matplotlib, [amg] for the AMG preconditioner
46
+
47
+ ## The simple way
48
+
49
+ ```python
50
+ import sdos
51
+ res = sdos.dos("H.npz") # a file (triplets, Matrix Market, .npz), a SciPy sparse matrix, an array, or a LinearOperator
52
+ res.plot() # DOS with error bars
53
+ E, rho = res.E, res.rho # rho: per orbital, integrates to 1 (multiply by N for states per unit energy)
54
+ res.save("dos.txt")
55
+ ```
56
+
57
+ Real or complex is detected, defaults are sensible (256 moments, 10 random vectors, first 4 moments exact), and nothing else is
58
+ needed. From the shell: `sdos H.npz -o dos.dat --plot dos.png`.
59
+
60
+ **Input.** The simplest matrix file is plain text, one entry per line, `i j a_ij` (a fourth column is the imaginary part).
61
+ Give one triangle or both; 0- or 1-based indices and the storage are detected automatically, and the other triangle is
62
+ generated by conjugation. Matrix Market and SciPy `.npz` also work.
63
+
64
+ **Save, then analyse later.** The DOS stage needs only the matrix. Save everything it produced (DOS, error bars, moments,
65
+ spectral window, settings) and analyse it afterwards, with no matrix:
66
+
67
+ sdos H.dat -o result.npz # a .npz name saves the full result
68
+ sdos analyze result.npz --valence-file Z.txt # electrons: E_F and band energy (Z of each atom, one per line)
69
+ sdos analyze result.npz --electrons 4096 --spin 2
70
+ sdos analyze phonons.npz --thermo 10 1000 50 # vibrations: ZPE, U, F, S, Cv (result made with --omega)
71
+ sdos analyze result.npz --moments 60 --method maxent -o dos.txt # re-reconstruct from the saved moments
72
+
73
+ In Python: `res.save("result.npz")`, `res = sdos.load("result.npz")`. The DOS is generic; electron counts, spin and
74
+ temperature enter only in the analysis step.
75
+
76
+ ```python
77
+ sdos.dos(H, resolution=0.05) # choose the energy resolution instead of the number of moments
78
+ sdos.dos(H, quick=True) # fast look: 128 moments, 4 vectors
79
+ sdos.dos(H, B=S) # generalized problem: overlap S (sparse), or masses (1-D array)
80
+ res = sdos.vdos(Phi, mass=m) # phonons: res.omega, res.g (= 2 w rho(w^2)); negative lambda are reported, never clipped
81
+ ```
82
+
83
+ ## All the extras, when you need them
84
+
85
+ Every option below is a keyword of `sdos.dos` / `sdos.estimate_dos` (and a flag of the `sdos` command).
86
+
87
+ | you want | use |
88
+ |---|---|
89
+ | sharper features from few moments | `method="maxent"` (with moment uncertainties and jackknife errors) |
90
+ | damping kernel | `kernel="jackson"` (default) or `"lorentz"` (a Green-function kernel: it spikes at the window edges for a DOS) |
91
+ | lower noise in band energy / Fermi level | `exact_moments=K` (local O(N) method, unbiased; standard or diagonal B), or `vectors="impartial"` (Drabold-Sankey, first 3 moments exact per vector) |
92
+ | variance reduction by probing | `probe=d` (distance-d coloring: moments n ≤ d exact) |
93
+ | other random vectors | `vectors="phase" / "rademacher" / "gaussian" / "impartial"` |
94
+ | generalized problem `A x = λ B x` | `B=...`; `binv="pcg"` (default; `precond="block-jacobi"`, `"ic"` incomplete Cholesky, `"amg"`, `"none"`, experimental `"ilu"`) or `binv="chebyshev"` (fixed polynomial B⁻¹) |
95
+ | per-site blocks | `block_size=4` (orbitals per atom) for block-Jacobi / scaling |
96
+ | a window with sharp levels | `sdos.shift_invert_dos(A, (E1, E2))` — **not O(N)** |
97
+ | Fermi level and band energy | `sdos.fermi_and_band_energy(res, n_electrons)` |
98
+ | eigenstates in a window + IPR | `sdos.window_eigs(A, B, (E1, E2), method="filter")` (scalable) or `"shift-invert"` (exact, not O(N)) |
99
+ | local / projected DOS (Mulliken for B != 1) | `sdos.pdos(A, B, groups=...)`, `sdos.ldos(A, B, orbitals=...)` |
100
+ | phonon thermodynamics | `sdos.vdos(Phi, mass=m, zero_modes=3)` then `sdos.harmonic_thermo(res, T)` |
101
+ | DOS from forces only (e.g. LAMMPS) | `sdos.interfaces.lmp.HessianOperator` + `sdos.vdos(op, hermitian_tol=1e-3)` |
102
+ | H(k), S(k) from SIESTA/sisl | `sdos.interfaces.sisl_if.from_sisl(H, k)` |
103
+
104
+ ## What to know
105
+
106
+ * The Lanczos spectral bounds are estimates inside the spectrum; the default 1% margin covers the usual error and a warning is
107
+ issued if a moment exceeds 1 (bounds too tight). Pass `bounds=(Emin, Emax)` for guaranteed values (Gershgorin: `sdos.spectral_bounds(A, "gershgorin")`).
108
+ * Generalized problems: exact local moments and impartial vectors need a local operator and are refused for a non-diagonal B.
109
+ Probing is not available for them. A diagonal B (masses) is mass-weighted once and no solves are done.
110
+ * Shift-invert windows factorize `A - σ`; the fill grows superlinearly in 3D, so they are not O(N).
111
+ * License: BSD-3-Clause.
112
+
113
+ ## Citation
114
+
115
+ Archived on Zenodo, concept DOI [10.5281/zenodo.24023874](https://doi.org/10.5281/zenodo.24023874) (always the latest version); see `CITATION.cff`.
116
+
sdos-0.1.0/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # sdos
2
+
3
+ The density of states of a big sparse matrix, in one call. Order-N stochastic trace estimation with the kernel polynomial
4
+ method (KPM) or maximum entropy; real symmetric or complex Hermitian; explicit or matrix-free; standard `A x = λ x` or
5
+ generalized `A x = λ B x`; electrons or phonons.
6
+
7
+ Status: **0.1.0, first public release.** The method, its tests against dense diagonalization and an independent code, benchmarks up to
8
+ millions of atoms, and the limits of the approach (what does not scale) are described in the report in `paper/` (REVTeX source and bibliography).
9
+ Docs: `docs/guide.md`, `docs/theory.md`. Tests: `pip install -e ".[test]" && pytest`.
10
+
11
+ **In short.** `sdos` estimates the density of states of a large sparse Hermitian matrix (or a generalized problem with an overlap or mass matrix)
12
+ from Chebyshev moments computed with random vectors, so the cost grows linearly with the matrix size. The density is reconstructed by the kernel
13
+ polynomial method or by maximum entropy, and the same moments give band energies, Fermi levels, local and projected densities and harmonic
14
+ thermodynamics. Interfaces: tight-binding and DFTB Hamiltonians, SIESTA through sisl, and matrix-free LAMMPS Hessians for phonons.
15
+ The underlying methods are due to Silver, Röder, Kress, Voter, Drabold, Sankey and collaborators; the report states what is added.
16
+
17
+ ## Install
18
+
19
+ pip install -e ".[test]" # from a clone; add [plot] for matplotlib, [amg] for the AMG preconditioner
20
+
21
+ ## The simple way
22
+
23
+ ```python
24
+ import sdos
25
+ res = sdos.dos("H.npz") # a file (triplets, Matrix Market, .npz), a SciPy sparse matrix, an array, or a LinearOperator
26
+ res.plot() # DOS with error bars
27
+ E, rho = res.E, res.rho # rho: per orbital, integrates to 1 (multiply by N for states per unit energy)
28
+ res.save("dos.txt")
29
+ ```
30
+
31
+ Real or complex is detected, defaults are sensible (256 moments, 10 random vectors, first 4 moments exact), and nothing else is
32
+ needed. From the shell: `sdos H.npz -o dos.dat --plot dos.png`.
33
+
34
+ **Input.** The simplest matrix file is plain text, one entry per line, `i j a_ij` (a fourth column is the imaginary part).
35
+ Give one triangle or both; 0- or 1-based indices and the storage are detected automatically, and the other triangle is
36
+ generated by conjugation. Matrix Market and SciPy `.npz` also work.
37
+
38
+ **Save, then analyse later.** The DOS stage needs only the matrix. Save everything it produced (DOS, error bars, moments,
39
+ spectral window, settings) and analyse it afterwards, with no matrix:
40
+
41
+ sdos H.dat -o result.npz # a .npz name saves the full result
42
+ sdos analyze result.npz --valence-file Z.txt # electrons: E_F and band energy (Z of each atom, one per line)
43
+ sdos analyze result.npz --electrons 4096 --spin 2
44
+ sdos analyze phonons.npz --thermo 10 1000 50 # vibrations: ZPE, U, F, S, Cv (result made with --omega)
45
+ sdos analyze result.npz --moments 60 --method maxent -o dos.txt # re-reconstruct from the saved moments
46
+
47
+ In Python: `res.save("result.npz")`, `res = sdos.load("result.npz")`. The DOS is generic; electron counts, spin and
48
+ temperature enter only in the analysis step.
49
+
50
+ ```python
51
+ sdos.dos(H, resolution=0.05) # choose the energy resolution instead of the number of moments
52
+ sdos.dos(H, quick=True) # fast look: 128 moments, 4 vectors
53
+ sdos.dos(H, B=S) # generalized problem: overlap S (sparse), or masses (1-D array)
54
+ res = sdos.vdos(Phi, mass=m) # phonons: res.omega, res.g (= 2 w rho(w^2)); negative lambda are reported, never clipped
55
+ ```
56
+
57
+ ## All the extras, when you need them
58
+
59
+ Every option below is a keyword of `sdos.dos` / `sdos.estimate_dos` (and a flag of the `sdos` command).
60
+
61
+ | you want | use |
62
+ |---|---|
63
+ | sharper features from few moments | `method="maxent"` (with moment uncertainties and jackknife errors) |
64
+ | damping kernel | `kernel="jackson"` (default) or `"lorentz"` (a Green-function kernel: it spikes at the window edges for a DOS) |
65
+ | lower noise in band energy / Fermi level | `exact_moments=K` (local O(N) method, unbiased; standard or diagonal B), or `vectors="impartial"` (Drabold-Sankey, first 3 moments exact per vector) |
66
+ | variance reduction by probing | `probe=d` (distance-d coloring: moments n ≤ d exact) |
67
+ | other random vectors | `vectors="phase" / "rademacher" / "gaussian" / "impartial"` |
68
+ | generalized problem `A x = λ B x` | `B=...`; `binv="pcg"` (default; `precond="block-jacobi"`, `"ic"` incomplete Cholesky, `"amg"`, `"none"`, experimental `"ilu"`) or `binv="chebyshev"` (fixed polynomial B⁻¹) |
69
+ | per-site blocks | `block_size=4` (orbitals per atom) for block-Jacobi / scaling |
70
+ | a window with sharp levels | `sdos.shift_invert_dos(A, (E1, E2))` — **not O(N)** |
71
+ | Fermi level and band energy | `sdos.fermi_and_band_energy(res, n_electrons)` |
72
+ | eigenstates in a window + IPR | `sdos.window_eigs(A, B, (E1, E2), method="filter")` (scalable) or `"shift-invert"` (exact, not O(N)) |
73
+ | local / projected DOS (Mulliken for B != 1) | `sdos.pdos(A, B, groups=...)`, `sdos.ldos(A, B, orbitals=...)` |
74
+ | phonon thermodynamics | `sdos.vdos(Phi, mass=m, zero_modes=3)` then `sdos.harmonic_thermo(res, T)` |
75
+ | DOS from forces only (e.g. LAMMPS) | `sdos.interfaces.lmp.HessianOperator` + `sdos.vdos(op, hermitian_tol=1e-3)` |
76
+ | H(k), S(k) from SIESTA/sisl | `sdos.interfaces.sisl_if.from_sisl(H, k)` |
77
+
78
+ ## What to know
79
+
80
+ * The Lanczos spectral bounds are estimates inside the spectrum; the default 1% margin covers the usual error and a warning is
81
+ issued if a moment exceeds 1 (bounds too tight). Pass `bounds=(Emin, Emax)` for guaranteed values (Gershgorin: `sdos.spectral_bounds(A, "gershgorin")`).
82
+ * Generalized problems: exact local moments and impartial vectors need a local operator and are refused for a non-diagonal B.
83
+ Probing is not available for them. A diagonal B (masses) is mass-weighted once and no solves are done.
84
+ * Shift-invert windows factorize `A - σ`; the fill grows superlinearly in 3D, so they are not O(N).
85
+ * License: BSD-3-Clause.
86
+
87
+ ## Citation
88
+
89
+ Archived on Zenodo, concept DOI [10.5281/zenodo.24023874](https://doi.org/10.5281/zenodo.24023874) (always the latest version); see `CITATION.cff`.
90
+
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sdos"
7
+ version = "0.1.0"
8
+ description = "Order-N spectral density of states (KPM, Maxent, shift-invert) for large sparse Hermitian problems"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "BSD-3-Clause"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "David A. Drabold", email = "drabold@ohio.edu" }]
14
+ keywords = ["density of states", "kernel polynomial method", "maximum entropy", "sparse eigenvalue", "tight binding", "vibrational spectra"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Science/Research",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Scientific/Engineering :: Physics",
20
+ ]
21
+ dependencies = ["numpy>=1.22", "scipy>=1.8"]
22
+
23
+ [project.optional-dependencies]
24
+ plot = ["matplotlib"]
25
+ amg = ["pyamg"]
26
+ sisl = ["sisl"]
27
+ test = ["pytest"]
28
+
29
+ [project.scripts]
30
+ sdos = "sdos.cli:main"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests"]
37
+ markers = ["slow: slower tests excluded from CI (run with -m slow)"]
sdos-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ """sdos: order-N spectral density of states for large sparse Hermitian problems.
2
+
3
+ Stochastic trace estimation (Drabold-Sankey) with the kernel polynomial method or maximum entropy,
4
+ plus shift-invert windows. Works for real symmetric and complex Hermitian matrices, explicit or
5
+ matrix-free. See ``sdos.estimate_dos`` and the ``sdos`` command.
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ from .api import dos
11
+ from .bounds import distance_coloring, spectral_bounds
12
+ from .dos import DOSResult, estimate_dos, fermi_and_band_energy, fermi_interval
13
+ from .results import load, save_result
14
+ from .io import read_matrix, write_dos, write_triplets
15
+ from .maxent import band_quantities_grid, maxent, maxent_bootstrap, maxent_density, maxent_fit
16
+ from .moments import chebyshev_moments, chebyshev_moments_plain, impartial_vector, make_vectors, moment_errors, stochastic_moments
17
+ from .operators import HermitianError, ScaledOperator, check_hermitian
18
+ from .reconstruct import band_quantities, jackson, lorentz, reconstruct
19
+ from .eigwin import WindowResult, count_states_below, homo_lumo, ipr, mulliken_weights, window_eigs
20
+ from .exact import exact_chebyshev_moments
21
+ from .generalized import GeneralizedOperator, generalized_bounds, pcg
22
+ from .phonon import vdos
23
+ from .projections import ProjectedResult, ldos, pdos, site_groups
24
+ from .thermo import HBAR_OMEGA_METAL, ThermoResult, harmonic_thermo
25
+ from .windows import shift_invert_dos, si_nodes
26
+
27
+ __all__ = [
28
+ "__version__", "dos", "load", "save_result", "vdos", "DOSResult", "estimate_dos", "fermi_and_band_energy", "fermi_interval", "read_matrix", "write_dos",
29
+ "write_triplets", "spectral_bounds", "distance_coloring", "chebyshev_moments", "make_vectors",
30
+ "stochastic_moments", "moment_errors", "impartial_vector", "jackson", "lorentz", "reconstruct", "band_quantities",
31
+ "maxent", "maxent_bootstrap", "maxent_fit", "maxent_density", "band_quantities_grid", "shift_invert_dos", "si_nodes",
32
+ "check_hermitian", "HermitianError", "ScaledOperator", "exact_chebyshev_moments", "GeneralizedOperator",
33
+ "generalized_bounds", "pcg", "window_eigs", "WindowResult", "count_states_below", "homo_lumo", "harmonic_thermo", "ThermoResult", "HBAR_OMEGA_METAL", "pdos", "ldos", "site_groups", "ProjectedResult", "ipr", "mulliken_weights", "chebyshev_moments_plain",
34
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
@@ -0,0 +1,80 @@
1
+ """The simple front door: ``sdos.dos(A)`` gives the density of states of a big sparse matrix with sensible defaults;
2
+ every advanced option of :func:`sdos.estimate_dos` is available as a keyword when needed."""
3
+
4
+ import os
5
+
6
+ import warnings
7
+ import numpy as np
8
+ import scipy.sparse as sp
9
+ import scipy.sparse.linalg as spla
10
+
11
+ from .bounds import spectral_bounds
12
+ from .dos import estimate_dos
13
+ from .generalized import GeneralizedOperator, generalized_bounds, is_diagonal
14
+ from .io import read_matrix
15
+ from .operators import is_complex
16
+
17
+
18
+ def _as_matrix(A, name):
19
+ if isinstance(A, (str, os.PathLike)):
20
+ return read_matrix(A, verbose=False)
21
+ if isinstance(A, np.ndarray) and A.ndim == 2:
22
+ return sp.csr_matrix(A)
23
+ if sp.issparse(A):
24
+ return A.tocsr()
25
+ if isinstance(A, spla.LinearOperator):
26
+ return A
27
+ raise TypeError(f"{name}: expected a file path, a 2-D array, a SciPy sparse matrix or a LinearOperator")
28
+
29
+
30
+ def dos(A, B=None, *, M=None, R=None, resolution=None, quick=False, exact_moments="auto", method="kpm", seed=None,
31
+ verbose=False, **options):
32
+ """Density of states of a large sparse Hermitian matrix A (or of A x = lambda B x).
33
+
34
+ ``A`` and ``B`` can be a file name (triplets, Matrix Market or .npz), a dense/sparse matrix, or (A only) a matrix-free
35
+ ``LinearOperator``; a 1-D ``B`` is a diagonal B (e.g. masses). Real or complex Hermitian is detected automatically.
36
+
37
+ Defaults: 256 moments and 10 random vectors (``quick=True``: 128 and 4), Jackson kernel, the first 4 moments exact (cheap local
38
+ method; set ``exact_moments=0`` to turn it off; the default is 0 for method="maxent" with R = 1), PCG with block-Jacobi for a generalized B. Give an energy ``resolution`` instead
39
+ of ``M`` to choose the number of moments from it. Anything else of :func:`sdos.estimate_dos` can be passed as a keyword
40
+ (``method='maxent'``, ``vectors='impartial'``, ``binv='chebyshev'``, ``probe=2`` ...).
41
+
42
+ Returns a :class:`~sdos.DOSResult`: ``res.E``, ``res.rho`` (per orbital, integrates to 1), ``res.rho_err``, ``res.idos``,
43
+ ``res.plot()``, ``res.save(path)``. Multiply ``rho`` by N for states per unit energy.
44
+ """
45
+ A = _as_matrix(A, "A")
46
+ if B is not None and not (isinstance(B, np.ndarray) and B.ndim == 1):
47
+ B = _as_matrix(B, "B")
48
+ if R is None:
49
+ R = 4 if quick else 10
50
+ explicit = sp.issparse(A)
51
+ diag_B = B is None or (isinstance(B, np.ndarray) and B.ndim == 1) or (sp.issparse(B) and is_diagonal(B))
52
+ if exact_moments == "auto":
53
+ exact_moments = 4 if (explicit and diag_B and method == "kpm" or explicit and diag_B and method == "maxent") else 0
54
+ if method == "maxent" and R < 2:
55
+ exact_moments = 0 # Maxent with one vector and spliced moments does not converge (see estimate_dos); no substitution by default
56
+ if M is None and resolution is not None:
57
+ # window width from the bounds, then M ~ pi a / resolution
58
+ if B is None or diag_B:
59
+ if B is not None:
60
+ d = np.real(B.diagonal()) if sp.issparse(B) else np.asarray(B, float)
61
+ Aw = sp.diags(1 / np.sqrt(d)) @ A @ sp.diags(1 / np.sqrt(d))
62
+ else:
63
+ Aw = A
64
+ lo, hi = spectral_bounds(Aw, verbose=False)
65
+ else:
66
+ op = GeneralizedOperator(A, B, binv=options.get("binv", "pcg"), tol=options.get("binv_tol", 1e-12),
67
+ block_size=options.get("block_size", 1), check=False)
68
+ lo, hi, rs = generalized_bounds(op.apply, op.B, A.shape[0], seed=0, is_cplx=bool(is_complex(A)))
69
+ lo, hi = lo - rs, hi + rs
70
+ M = int(np.ceil(np.pi * 0.5 * (hi - lo) * 1.02 / resolution))
71
+ if M is None:
72
+ M = 128 if quick else 256
73
+ if method == "maxent":
74
+ options.setdefault("maxent_default", "flat") # uniform default model; measured equal in accuracy and conditioning to the alternatives (docs/theory.md)
75
+ if M >= A.shape[0]:
76
+ warnings.warn(f"M={M} >= N={A.shape[0]}: the resolution pi*a/M is finer than the mean level spacing, the density resolves individual levels and sampling speckle; "
77
+ "for such a small matrix dense diagonalization (numpy.linalg.eigvalsh) is cheaper and exact.", stacklevel=2)
78
+ if exact_moments and exact_moments >= M:
79
+ exact_moments = max(0, M - 1)
80
+ return estimate_dos(A, B=B, M=M, R=R, method=method, seed=seed, verbose=verbose, exact_moments=exact_moments, **options)
@@ -0,0 +1,67 @@
1
+ """Spectral bounds and probing colorings."""
2
+
3
+ import sys
4
+
5
+ import numpy as np
6
+ import scipy.sparse as sp
7
+ import scipy.sparse.linalg as spla
8
+
9
+
10
+ def spectral_bounds(H, method="lanczos", verbose=True, v0_seed=12345):
11
+ """Return (Emin, Emax): guaranteed (Gershgorin, explicit matrices only) or estimated (Lanczos).
12
+
13
+ Lanczos extremal Ritz values lie *inside* the spectrum, so callers add a safety margin
14
+ (``estimate_dos(margin=...)``); for matrix-free operators only the Lanczos route exists.
15
+ The ARPACK start vector is generated from ``v0_seed`` so the bounds (and hence the whole DOS for a given ``seed``) are
16
+ reproducible; the original sdos.py let ARPACK pick a random start, so repeated runs differed at the 1e-4 level in the bounds.
17
+ """
18
+ n = H.shape[0]
19
+ explicit = sp.issparse(H)
20
+ if explicit:
21
+ absH = abs(H)
22
+ d = np.real(H.diagonal())
23
+ radius = np.asarray(absH.sum(axis=1)).ravel() - np.abs(d)
24
+ gmin, gmax = float((d - radius).min()), float((d + radius).max())
25
+ if method == "gershgorin" or n < 3:
26
+ return gmin, gmax
27
+ elif method == "gershgorin":
28
+ raise ValueError("Gershgorin bounds need an explicit sparse matrix")
29
+ try:
30
+ v0 = np.random.default_rng(v0_seed).normal(size=n)
31
+ if np.issubdtype(H.dtype, np.complexfloating):
32
+ v0 = v0 + 1j * np.random.default_rng(v0_seed + 1).normal(size=n)
33
+ kw = dict(k=1, tol=1e-3, maxiter=max(300, 10 * int(np.sqrt(n))), v0=v0)
34
+ emax = float(np.real(spla.eigsh(H, which="LA", return_eigenvectors=False, **kw)[0]))
35
+ emin = float(np.real(spla.eigsh(H, which="SA", return_eigenvectors=False, **kw)[0]))
36
+ return emin, emax
37
+ except Exception as exc: # fall back to the safe bounds when they exist
38
+ if not explicit:
39
+ raise
40
+ if verbose:
41
+ print(f"Lanczos bounds failed ({exc}); using Gershgorin", file=sys.stderr)
42
+ return gmin, gmax
43
+
44
+
45
+ def distance_coloring(H, d):
46
+ """Greedy coloring such that same-colored sites are > d hops apart (explicit matrices only)."""
47
+ n = H.shape[0]
48
+ G = H.copy().tocsr()
49
+ G.data = np.ones_like(G.data, dtype=np.int8)
50
+ G = (G + sp.identity(n, dtype=np.int8, format="csr")).astype(bool).astype(np.int8)
51
+ P = G.copy()
52
+ for _ in range(d - 1):
53
+ P = (P @ G).astype(bool).astype(np.int8)
54
+ P = P.tocsr()
55
+ indptr, indices = P.indptr, P.indices
56
+ colors = -np.ones(n, dtype=np.int64)
57
+ mark = -np.ones(n + 1, dtype=np.int64) # mark[c] = node that saw color c
58
+ order = np.argsort(-np.diff(indptr), kind="stable") # high degree first
59
+ for i in order:
60
+ nb = indices[indptr[i]:indptr[i + 1]]
61
+ cs = colors[nb]
62
+ mark[cs[cs >= 0]] = i
63
+ c = 0
64
+ while mark[c] == i:
65
+ c += 1
66
+ colors[i] = c
67
+ return colors, int(colors.max()) + 1