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.
- sdos-0.1.0/LICENSE +29 -0
- sdos-0.1.0/PKG-INFO +116 -0
- sdos-0.1.0/README.md +90 -0
- sdos-0.1.0/pyproject.toml +37 -0
- sdos-0.1.0/setup.cfg +4 -0
- sdos-0.1.0/src/sdos/__init__.py +34 -0
- sdos-0.1.0/src/sdos/__main__.py +3 -0
- sdos-0.1.0/src/sdos/api.py +80 -0
- sdos-0.1.0/src/sdos/bounds.py +67 -0
- sdos-0.1.0/src/sdos/cli.py +186 -0
- sdos-0.1.0/src/sdos/dos.py +352 -0
- sdos-0.1.0/src/sdos/eigwin.py +279 -0
- sdos-0.1.0/src/sdos/exact.py +58 -0
- sdos-0.1.0/src/sdos/generalized.py +284 -0
- sdos-0.1.0/src/sdos/ic.py +82 -0
- sdos-0.1.0/src/sdos/interfaces/__init__.py +5 -0
- sdos-0.1.0/src/sdos/interfaces/_assemble.py +42 -0
- sdos-0.1.0/src/sdos/interfaces/kwon_si.py +165 -0
- sdos-0.1.0/src/sdos/interfaces/lmp.py +97 -0
- sdos-0.1.0/src/sdos/interfaces/sisl_if.py +34 -0
- sdos-0.1.0/src/sdos/interfaces/skf.py +253 -0
- sdos-0.1.0/src/sdos/interfaces/xu_carbon.py +82 -0
- sdos-0.1.0/src/sdos/io.py +120 -0
- sdos-0.1.0/src/sdos/maxent.py +231 -0
- sdos-0.1.0/src/sdos/moments.py +147 -0
- sdos-0.1.0/src/sdos/operators.py +95 -0
- sdos-0.1.0/src/sdos/phonon.py +58 -0
- sdos-0.1.0/src/sdos/projections.py +188 -0
- sdos-0.1.0/src/sdos/reconstruct.py +72 -0
- sdos-0.1.0/src/sdos/results.py +176 -0
- sdos-0.1.0/src/sdos/thermo.py +85 -0
- sdos-0.1.0/src/sdos/windows.py +125 -0
- sdos-0.1.0/src/sdos.egg-info/PKG-INFO +116 -0
- sdos-0.1.0/src/sdos.egg-info/SOURCES.txt +56 -0
- sdos-0.1.0/src/sdos.egg-info/dependency_links.txt +1 -0
- sdos-0.1.0/src/sdos.egg-info/entry_points.txt +2 -0
- sdos-0.1.0/src/sdos.egg-info/requires.txt +14 -0
- sdos-0.1.0/src/sdos.egg-info/top_level.txt +1 -0
- sdos-0.1.0/tests/test_exact.py +73 -0
- sdos-0.1.0/tests/test_exact_and_api.py +133 -0
- sdos-0.1.0/tests/test_examples.py +16 -0
- sdos-0.1.0/tests/test_fullerene.py +106 -0
- sdos-0.1.0/tests/test_generalized.py +130 -0
- sdos-0.1.0/tests/test_ic.py +62 -0
- sdos-0.1.0/tests/test_impartial.py +57 -0
- sdos-0.1.0/tests/test_interfaces.py +85 -0
- sdos-0.1.0/tests/test_io.py +35 -0
- sdos-0.1.0/tests/test_kwon_si.py +59 -0
- sdos-0.1.0/tests/test_maxent_bootstrap.py +39 -0
- sdos-0.1.0/tests/test_maxent_robust.py +52 -0
- sdos-0.1.0/tests/test_phonon.py +69 -0
- sdos-0.1.0/tests/test_projections.py +86 -0
- sdos-0.1.0/tests/test_regression.py +90 -0
- sdos-0.1.0/tests/test_results.py +67 -0
- sdos-0.1.0/tests/test_skf.py +183 -0
- sdos-0.1.0/tests/test_thermo.py +98 -0
- sdos-0.1.0/tests/test_window.py +113 -0
- 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,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,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
|