sdos 0.1.0__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.
- sdos/__init__.py +34 -0
- sdos/__main__.py +3 -0
- sdos/api.py +80 -0
- sdos/bounds.py +67 -0
- sdos/cli.py +186 -0
- sdos/dos.py +352 -0
- sdos/eigwin.py +279 -0
- sdos/exact.py +58 -0
- sdos/generalized.py +284 -0
- sdos/ic.py +82 -0
- sdos/interfaces/__init__.py +5 -0
- sdos/interfaces/_assemble.py +42 -0
- sdos/interfaces/kwon_si.py +165 -0
- sdos/interfaces/lmp.py +97 -0
- sdos/interfaces/sisl_if.py +34 -0
- sdos/interfaces/skf.py +253 -0
- sdos/interfaces/xu_carbon.py +82 -0
- sdos/io.py +120 -0
- sdos/maxent.py +231 -0
- sdos/moments.py +147 -0
- sdos/operators.py +95 -0
- sdos/phonon.py +58 -0
- sdos/projections.py +188 -0
- sdos/reconstruct.py +72 -0
- sdos/results.py +176 -0
- sdos/thermo.py +85 -0
- sdos/windows.py +125 -0
- sdos-0.1.0.dist-info/METADATA +116 -0
- sdos-0.1.0.dist-info/RECORD +33 -0
- sdos-0.1.0.dist-info/WHEEL +5 -0
- sdos-0.1.0.dist-info/entry_points.txt +2 -0
- sdos-0.1.0.dist-info/licenses/LICENSE +29 -0
- sdos-0.1.0.dist-info/top_level.txt +1 -0
sdos/__init__.py
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
|
+
]
|
sdos/__main__.py
ADDED
sdos/api.py
ADDED
|
@@ -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)
|
sdos/bounds.py
ADDED
|
@@ -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
|
sdos/cli.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Command-line interface: ``sdos H.dat -M 512 -R 10 --probe 2 -o dos.dat --electrons 4096``."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from .api import dos as dos_api
|
|
10
|
+
from .dos import fermi_and_band_energy
|
|
11
|
+
from .phonon import vdos
|
|
12
|
+
from .io import read_matrix, write_dos
|
|
13
|
+
from .windows import shift_invert_dos
|
|
14
|
+
|
|
15
|
+
__doc__ += """
|
|
16
|
+
|
|
17
|
+
Run ``sdos -h`` for all options. Input: triplets ``i j Re(A_ij) [Im(A_ij)]`` (0/1-based, one
|
|
18
|
+
triangle or both), Matrix Market, or SciPy ``.npz``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def main(argv=None):
|
|
22
|
+
if (argv if argv is not None else sys.argv[1:])[:1] == ["analyze"]:
|
|
23
|
+
from .results import analyze
|
|
24
|
+
return analyze((argv if argv is not None else sys.argv[1:])[1:])
|
|
25
|
+
p = argparse.ArgumentParser(prog="sdos", description=__doc__,
|
|
26
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
27
|
+
p.add_argument("--version", action="version", version=f"sdos {__version__}")
|
|
28
|
+
p.add_argument("matrix", help="triplet file: i j Re [Im] (or Matrix Market)")
|
|
29
|
+
p.add_argument("-M", "--moments", type=int, default=256,
|
|
30
|
+
help="number of Chebyshev moments (resolution ~ pi*halfwidth/M)")
|
|
31
|
+
p.add_argument("-R", "--samples", type=int, default=10,
|
|
32
|
+
help="number of independent random samples (for averaging and error bars)")
|
|
33
|
+
p.add_argument("--probe", type=int, default=0,
|
|
34
|
+
help="distance-d coloring for probing (0 = plain random vectors)")
|
|
35
|
+
p.add_argument("--B", default=None, help="second matrix B of A x = lambda B x (same formats as the matrix; Hermitian positive definite)")
|
|
36
|
+
p.add_argument("--mass", default=None, help="text file with one mass (diagonal B) per degree of freedom: mass-weights once, no solves")
|
|
37
|
+
p.add_argument("--binv", choices=["pcg", "chebyshev"], default="pcg", help="how B^-1 is applied: PCG, or a fixed Chebyshev polynomial")
|
|
38
|
+
p.add_argument("--precond", choices=["block-jacobi", "ilu", "amg", "none"], default="block-jacobi", help="PCG preconditioner")
|
|
39
|
+
p.add_argument("--block-size", type=int, default=1, help="per-site block size for block-Jacobi / scaling (e.g. orbitals per atom)")
|
|
40
|
+
p.add_argument("--binv-tol", type=float, default=1e-12, help="PCG relative tolerance / polynomial accuracy")
|
|
41
|
+
p.add_argument("--exact-moments", default="auto", help="'auto' (first 4 moments exact when cheap), 0 (off) or K")
|
|
42
|
+
p.add_argument("--thermo", type=float, nargs=3, metavar=("TMIN", "TMAX", "NT"), help="with --omega: harmonic thermodynamics (U, F, S, Cv per atom) on a T grid, "
|
|
43
|
+
"written to <output>.thermo (lambda in eV/(A^2 amu); use --zero-modes 3 for crystals/molecules)")
|
|
44
|
+
p.add_argument("--zero-modes", type=int, default=0, help="number of known zero modes (rigid translations: 3) to remove from the moments exactly")
|
|
45
|
+
p.add_argument("--omega", action="store_true", help="phonon output: write omega, g(omega)=2 omega rho(omega^2), error (lambda = omega^2)")
|
|
46
|
+
p.add_argument("--window", type=float, nargs=2, metavar=("E1", "E2"), help="eigenpairs inside [E1, E2] with the IPR of each state (see --window-method)")
|
|
47
|
+
p.add_argument("--window-method", choices=["auto", "shift-invert", "filter"], default="auto",
|
|
48
|
+
help="shift-invert (exact, NOT O(N)) or Chebyshev-filtered subspace iteration (scalable)")
|
|
49
|
+
p.add_argument("--window-degree", type=int, default=300, help="filter polynomial degree")
|
|
50
|
+
p.add_argument("--vectors-out", default=None, help="save the window eigenvectors (npz) as well")
|
|
51
|
+
p.add_argument("--base", choices=["auto", "0", "1"], default="auto")
|
|
52
|
+
p.add_argument("--format", choices=["auto", "triplet", "mm", "npz"], default="auto", help="input format")
|
|
53
|
+
p.add_argument("--kernel", choices=["jackson", "lorentz"], default="jackson", help="KPM damping kernel")
|
|
54
|
+
p.add_argument("--lorentz-lambda", type=float, default=4.0, help="lambda of the Lorentz kernel")
|
|
55
|
+
p.add_argument("--vectors", choices=["auto", "phase", "rademacher", "gaussian", "impartial"], default="auto", help="random-vector ensemble; impartial = Drabold-Sankey penalty-function vectors (Maxent-friendly, first 3 moments exact)")
|
|
56
|
+
p.add_argument("--storage", choices=["auto", "triangle", "full"], default="auto")
|
|
57
|
+
p.add_argument("--emin", type=float, help="lower spectral bound (skip estimation)")
|
|
58
|
+
p.add_argument("--emax", type=float, help="upper spectral bound (skip estimation)")
|
|
59
|
+
p.add_argument("--complex-phases", action="store_true",
|
|
60
|
+
help="use exp(i phi) vectors even for real H (half the variance, 2x cost)")
|
|
61
|
+
p.add_argument("--seed", type=int, default=None)
|
|
62
|
+
p.add_argument("--npts", type=int, default=None, help="energy grid points")
|
|
63
|
+
p.add_argument("--method", choices=["kpm", "maxent", "si"], default="kpm",
|
|
64
|
+
help="kpm (Jackson kernel, default), maxent, or si (shift-and-invert "
|
|
65
|
+
"Lanczos in a window; needs --si-range)")
|
|
66
|
+
p.add_argument("--si-range", type=float, nargs=2, metavar=("E1", "E2"),
|
|
67
|
+
help="energy window for --method si (e.g. a gap/tail/defect region)")
|
|
68
|
+
p.add_argument("--si-shifts", type=int, default=1,
|
|
69
|
+
help="number of shifts (LU factorizations) spread over the window")
|
|
70
|
+
p.add_argument("--si-steps", type=int, default=150,
|
|
71
|
+
help="Lanczos steps per vector at each shift")
|
|
72
|
+
p.add_argument("--si-eta", type=float, default=None,
|
|
73
|
+
help="Gaussian broadening of Ritz nodes (default: mean node spacing)")
|
|
74
|
+
p.add_argument("--maxent-default", choices=["chebyshev", "flat", "kpm"], default="chebyshev",
|
|
75
|
+
help="Maxent default model: 1/(pi sqrt(1-x^2)) (default), flat, or kpm (the Jackson density; robust to a too-wide spectral window)")
|
|
76
|
+
p.add_argument("--maxent-noise", type=float, default=1.0,
|
|
77
|
+
help="scale on moment standard errors in the Maxent dual (0 = fit moments exactly)")
|
|
78
|
+
p.add_argument("--electrons", type=float, default=None,
|
|
79
|
+
help="number of electrons: report Fermi level and band energy")
|
|
80
|
+
p.add_argument("--spin", type=float, default=2.0, help="spin degeneracy (default 2)")
|
|
81
|
+
p.add_argument("-o", "--output", default="dos.dat",
|
|
82
|
+
help="output file; a name ending in .npz saves a full result file (moments, settings) for `sdos analyze`")
|
|
83
|
+
p.add_argument("--moments-out", default=None, help="write averaged moments to file")
|
|
84
|
+
p.add_argument("--plot", default=None, help="save a plot (png/pdf)")
|
|
85
|
+
args = p.parse_args(argv)
|
|
86
|
+
|
|
87
|
+
H = read_matrix(args.matrix, base=args.base, storage=args.storage, fmt=args.format)
|
|
88
|
+
bounds = None
|
|
89
|
+
if args.emin is not None and args.emax is not None:
|
|
90
|
+
bounds = (args.emin, args.emax)
|
|
91
|
+
if args.window is not None:
|
|
92
|
+
from .eigwin import window_eigs
|
|
93
|
+
Bw = None
|
|
94
|
+
if args.B:
|
|
95
|
+
Bw = read_matrix(args.B, base=args.base, storage=args.storage, fmt=args.format)
|
|
96
|
+
elif args.mass:
|
|
97
|
+
Bw = np.loadtxt(args.mass).ravel()
|
|
98
|
+
w = window_eigs(H, Bw, tuple(args.window), method=args.window_method, degree=args.window_degree,
|
|
99
|
+
block_size=args.block_size, precond=args.precond)
|
|
100
|
+
np.savetxt(args.output, np.column_stack([w["E"], w["ipr"], w["residual"]]),
|
|
101
|
+
header=f"sdos window [{args.window[0]}, {args.window[1]}] method={w['info']['method']} n_found={len(w['E'])} "
|
|
102
|
+
f"(KPM estimate {w['info']['n_estimated']:.1f})\nE IPR residual", fmt="%.10e")
|
|
103
|
+
if args.vectors_out:
|
|
104
|
+
np.savez(args.vectors_out, E=w["E"], V=w["V"], ipr=w["ipr"])
|
|
105
|
+
print(f"{len(w['E'])} states in the window ({w['info']['method']}); wrote {args.output}")
|
|
106
|
+
return
|
|
107
|
+
if args.method == "si":
|
|
108
|
+
if args.si_range is None:
|
|
109
|
+
p.error("--method si requires --si-range E1 E2")
|
|
110
|
+
res = shift_invert_dos(H, args.si_range, nshift=args.si_shifts, m=args.si_steps,
|
|
111
|
+
R=args.samples, eta=args.si_eta, npts=args.npts,
|
|
112
|
+
seed=args.seed, complex_phases=args.complex_phases or None)
|
|
113
|
+
if args.electrons is not None:
|
|
114
|
+
print("note: --electrons ignored for --method si (window DOS only)", file=sys.stderr)
|
|
115
|
+
args.electrons = None
|
|
116
|
+
else:
|
|
117
|
+
Bobj = None
|
|
118
|
+
if args.B:
|
|
119
|
+
Bobj = read_matrix(args.B, base=args.base, storage=args.storage, fmt=args.format)
|
|
120
|
+
elif args.mass:
|
|
121
|
+
Bobj = np.loadtxt(args.mass).ravel()
|
|
122
|
+
exact = "auto" if args.exact_moments == "auto" else int(args.exact_moments)
|
|
123
|
+
kw = dict(M=args.moments, R=args.samples, probe=args.probe, bounds=bounds, complex_phases=args.complex_phases or None,
|
|
124
|
+
seed=args.seed, npts=args.npts, method=args.method, maxent_default=args.maxent_default,
|
|
125
|
+
maxent_noise=args.maxent_noise, kernel=args.kernel, lorentz_lambda=args.lorentz_lambda, vectors=args.vectors,
|
|
126
|
+
exact_moments=exact, binv=args.binv, precond=args.precond, block_size=args.block_size, binv_tol=args.binv_tol,
|
|
127
|
+
verbose=True)
|
|
128
|
+
res = vdos(H, B=Bobj, zero_modes=args.zero_modes, **kw) if args.omega else dos_api(H, B=Bobj, **kw)
|
|
129
|
+
|
|
130
|
+
N = res["N"]
|
|
131
|
+
if res["method"] == "kpm":
|
|
132
|
+
recon = f"Jackson resolution ~ {res['resolution']:.4g}"
|
|
133
|
+
elif res["method"] == "si":
|
|
134
|
+
recon = f"shift-invert Lanczos, window {res['erange']}, eta ~ {res['resolution']:.4g}"
|
|
135
|
+
else:
|
|
136
|
+
recon = f"Maxent, {res['maxent_default']} default"
|
|
137
|
+
header = (f"sdos {__version__} N={N} M={res['M']} samples={res['R']} colors={res['ncolors']} "
|
|
138
|
+
f"{recon}\n"
|
|
139
|
+
"E rho(E) [per orbital, integrates to 1] err IDOS(E) [fraction] err")
|
|
140
|
+
if args.output.endswith(".npz"):
|
|
141
|
+
from .results import save_result
|
|
142
|
+
save_result(args.output, res, {"command": " ".join(sys.argv[1:]) if argv is None else " ".join(argv), "matrix": args.matrix})
|
|
143
|
+
elif args.omega and "omega" in res:
|
|
144
|
+
np.savetxt(args.output, np.column_stack([res["omega"], res["g"], res["g_err"]]),
|
|
145
|
+
header=header + "\nomega g(omega)=2 omega rho(omega^2) [integrates to 1 - neg. weight] err", fmt="%.10e")
|
|
146
|
+
else:
|
|
147
|
+
np.savetxt(args.output, np.column_stack([res["E"], res["rho"], res["rho_err"],
|
|
148
|
+
res["idos"], res["idos_err"]]),
|
|
149
|
+
header=header, fmt="%.10e")
|
|
150
|
+
print(f"wrote {args.output}")
|
|
151
|
+
if args.thermo and args.omega:
|
|
152
|
+
from .thermo import harmonic_thermo
|
|
153
|
+
Tg = np.linspace(args.thermo[0], args.thermo[1], int(args.thermo[2]))
|
|
154
|
+
th = harmonic_thermo(res, Tg)
|
|
155
|
+
np.savetxt(args.output + ".thermo", np.column_stack([Tg, th["U"], th["F"], th["S"], th["Cv"]]),
|
|
156
|
+
header=f"harmonic thermodynamics per atom; zero-point energy {th['zpe']:.6g} eV; reliable for T >~ {th['info']['T_reliable']:.0f} K\nT[K] U[eV] F[eV] S[kB] Cv[kB]", fmt="%.8e")
|
|
157
|
+
print(f"wrote {args.output}.thermo (zero-point energy {th['zpe']:.5g} eV/atom; reliable for T >~ {th['info']['T_reliable']:.0f} K)")
|
|
158
|
+
if args.moments_out:
|
|
159
|
+
np.savetxt(args.moments_out, res["mu"], header="mu_n = Tr T_n(Ht)/N", fmt="%.12e")
|
|
160
|
+
|
|
161
|
+
if args.electrons is not None:
|
|
162
|
+
EF, Ebs, dEF, dEbs = fermi_and_band_energy(res, args.electrons, args.spin)
|
|
163
|
+
if res["R"] > 1:
|
|
164
|
+
print(f"E_F = {EF:.6f} +- {dEF:.2g} E_BS = {Ebs:.6f} +- {dEbs:.2g} "
|
|
165
|
+
f"({Ebs / args.electrons:.6f} per electron)")
|
|
166
|
+
else:
|
|
167
|
+
print(f"E_F = {EF:.6f} E_BS = {Ebs:.6f} ({Ebs / args.electrons:.6f} per electron)")
|
|
168
|
+
|
|
169
|
+
if args.plot:
|
|
170
|
+
import matplotlib
|
|
171
|
+
matplotlib.use("Agg")
|
|
172
|
+
import matplotlib.pyplot as plt
|
|
173
|
+
fig, ax = plt.subplots(figsize=(7, 4))
|
|
174
|
+
ax.plot(res["E"], res["rho"], lw=1)
|
|
175
|
+
if res["R"] > 1:
|
|
176
|
+
ax.fill_between(res["E"], res["rho"] - 2 * res["rho_err"],
|
|
177
|
+
res["rho"] + 2 * res["rho_err"], alpha=0.3, lw=0)
|
|
178
|
+
ax.set_xlabel("E")
|
|
179
|
+
ax.set_ylabel("DOS per orbital")
|
|
180
|
+
ax.set_title(f"N = {N}, M = {res['M']}, {res['R']} sample(s), {res['method'].upper()}")
|
|
181
|
+
fig.tight_layout()
|
|
182
|
+
fig.savefig(args.plot, dpi=150)
|
|
183
|
+
print(f"wrote {args.plot}")
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|