Skip to content

Commit

Permalink
added errors explaining which fdf-inputs are required for tbtrans
Browse files Browse the repository at this point in the history
This should make debugging errors for users simpler in cases
where there is a keyerror, and they think they can just extract
the orbital-transmissions from calculations which didn't calculate it.

Also fixed projections orbital-transmissions. I think the old one
didn't parse the projections correctly, now it should.

warnings can also be issued in case the users
wants to know what is missing etc.

This is still work in progress, but considering we could
add info stuff for the fdf sile.

Signed-off-by: Nick Papior <[email protected]>
  • Loading branch information
zerothi committed Jan 15, 2024
1 parent 3d9e347 commit d9acb0e
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 121 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ we hit release version 1.0.0.
## [0.14.4] - YYYY-MM-DD

### Added
- better error messages when users request quantities not calculated by Siesta/TBtrans
- `SparseCSR.toarray` to comply with array handling (equivalent to `todense`)
- enabled `Grid.to|new` with the most basic stuff
str|Path|Grid|pyamg
Expand Down
144 changes: 144 additions & 0 deletions src/sisl/io/_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import warnings
from functools import wraps
from typing import Callable, List, Tuple, Union

from sisl._internal import set_module
from sisl.messages import SislError, SislException, SislInfo, SislWarning, info, warn

__all__ = [
"SileError",
"SileWarning",
"SileInfo",
"MissingInputSileError",
"MissingInputSileInfo",
"MissingInputSileWarning",
"missing_input",
]


@set_module("sisl.io")
class SileError(SislError, IOError):
"""Define an error object related to the Sile objects"""

def __init__(self, value, obj=None):
self.value = value
self.obj = obj

def __str__(self):
if self.obj:
return f"{self.value!s} in {self.obj!s}"
return self.value


@set_module("sisl.io")
class SileWarning(SislWarning):
"""Warnings that informs users of things to be carefull about when using their retrieved data
These warnings should be issued whenever a read/write routine is unable to retrieve all information
but are non-influential in the sense that sisl is still able to perform the action.
"""


@set_module("sisl.io")
class SileInfo(SislInfo):
"""Information for the user, this is hidden in a warning, but is not as severe so as to issue a warning."""


InputsType = Union[List[Tuple[str, str]], List[str]]


class MissingInputSileException(SislException):
"""Container for constructing error/warnings when a fdf flag is missing from the input file.
This error message should preferably be raised through:
>>> raise InheritedClass(method, ["Diag.ParallelOverk"]) from exc
to retain a proper context.
"""

def __init__(
self,
executable: str,
inputs: InputsType,
method: Callable,
):
# Formulate the error message
try:
name = f"{method.__self__.__class__.__name__}.{method.__name__}"
except AttributeError:
name = f"{method.__name__}"

def parse(v):
if isinstance(v, str):
return f" * {v}"
return f" * {' '.join(v)}"

str_except = "\n".join(map(parse, inputs))

super().__init__(
f"Data from method '{name}' failed due to missing output values.\n\n"
f"This is because of missing options in the input file for executable {executable}.\n"
f"Please read up on the following flags in the manual of '{executable}' to figure out "
f"how to retrieve the expected quantities:\n{str_except}"
)


class MissingInputSileError(MissingInputSileException, SileError):
"""Issued when specific flags in the input file can be used to extract data"""


class MissingInputSileWarning(MissingInputSileException, SileWarning):
"""Issued when specific flags in the input file can be used to extract data"""


class MissingInputSileInfo(MissingInputSileException, SileInfo):
"""Issued when specific flags in the input file can be used to extract data"""


def missing_input(
executable: str,
inputs: InputsType,
what: MissingInputSileException,
when_exception: Exception = Exception,
):
"""Issues warnings, or errors based on `when` and `what`"""

def decorator(func):
what_inst = what(executable, inputs, func)

if issubclass(when_exception, Warning):
# we should do a warning catch thing
@wraps(func)
def deco(*args, **kwargs):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
ret = func(*args, **kwargs)

if len(w) > 0:
if isinstance(what_inst, MissingInputSileWarning):
warn(what_inst)
elif isinstance(what_inst, MissingInputSileInfo):
info(what_inst)
elif isinstance(what_inst, MissingInputSileError):
raise what_inst

return ret

else:
# it must be ane error to be raised
@wraps(func)
def deco(*args, **kwargs):
try:
return func(*args, **kwargs)
except what as ke:
raise what_inst from ke.__cause__
except when_exception as ke:
raise what_inst from ke

return deco

return decorator
31 changes: 29 additions & 2 deletions src/sisl/io/siesta/sile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from functools import wraps
from typing import Callable, List, Union

try:
from . import _siesta
Expand All @@ -11,9 +13,34 @@

from sisl._internal import set_module

from ..sile import Sile, SileBin, SileCDF, SileError
from ..sile import (
MissingInputSileError,
Sile,
SileBin,
SileCDF,
SileError,
missing_input,
)

__all__ = ["SileSiesta", "SileCDFSiesta", "SileBinSiesta"]
__all__ = [
"SileSiesta",
"SileCDFSiesta",
"SileBinSiesta",
"MissingFDFSiestaError",
"missing_input_fdf",
]


@set_module("sisl.io.siesta")
class MissingFDFSiestaError(MissingInputSileError):
pass


@set_module("sisl.io.siesta")
def missing_input_fdf(
inputs, executable: str = "siesta", when_exception: Exception = KeyError
):
return missing_input(executable, inputs, MissingFDFSiestaError, when_exception)


@set_module("sisl.io.siesta")
Expand Down
65 changes: 14 additions & 51 deletions src/sisl/io/sile.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
from os.path import basename, splitext
from pathlib import Path
from textwrap import dedent, indent
from typing import Any, Callable, Optional, Union
from typing import Any, Optional, Union

import sisl.io._exceptions as _exceptions
from sisl._environ import get_environ_variable
from sisl._internal import set_module
from sisl.messages import SislInfo, SislWarning, deprecate, info, warn
from sisl.utils.misc import str_spec

from ._exceptions import *
from ._help import *

# Public used objects
Expand All @@ -28,10 +30,8 @@
"Sile",
"SileCDF",
"SileBin",
"SileError",
"SileWarning",
"SileInfo",
]
__all__.extend(_exceptions.__all__)

# Decorators or sile-specific functions
__all__ += ["sile_fh_open", "sile_raise_write", "sile_raise_read"]
Expand Down Expand Up @@ -73,12 +73,11 @@ def __init__(self, cls, suffix, case=True, gzip=False):
self.base_names = [c.__name__.lower() for c in self.bases]

def __str__(self):
s = "{cls}{{case={case}, suffix={suffix}, gzip={gzip},\n ".format(
cls=self.cls.__name__, case=self.case, suffix=self.suffix, gzip=self.gzip
s = ",\n ".join(map(lambda x: x.__name, self.bases))
return (
f"{self.cls.__name__}{{case={self.case}, "
f"suffix={self.suffix}, gzip={self.gzip},\n {s}\n}}"
)
for b in self.bases:
s += f" {b.__name__},\n "
return s[:-3] + "\n}"

def __repr__(self):
return (
Expand Down Expand Up @@ -313,7 +312,7 @@ def has(keys):
# Check for files without ending, or that they are directly zipped
lext = splitext(f)
while len(lext[1]) > 0:
end = lext[1] + end
end = f"{lext[1]}{end}"
if end[0] == ".":
end_list.append(end[1:])
else:
Expand Down Expand Up @@ -521,8 +520,8 @@ def read(self, *args, **kwargs):
"""
for key in kwargs.keys():
# Loop all keys and try to read the quantities
if hasattr(self, "read_" + key):
func = getattr(self, "read_" + key)
if hasattr(self, f"read_{key}"):
func = getattr(self, f"read_{key}")
# Call read
return func(kwargs[key], **kwargs)

Expand All @@ -549,8 +548,8 @@ def write(self, *args, **kwargs):

for key in kwargs.keys():
# Loop all keys and try to write the quantities
if hasattr(self, "write_" + key):
func = getattr(self, "write_" + key)
if hasattr(self, f"write_{key}"):
func = getattr(self, f"write_{key}")
# Call write
func(kwargs[key], **kwargs)

Expand All @@ -561,7 +560,6 @@ def _setup(self, *args, **kwargs):
This method can be overwritten.
"""
pass

def _base_setup(self, *args, **kwargs):
"""Setup the `Sile` after initialization
Expand Down Expand Up @@ -633,7 +631,6 @@ def ArgumentParser_out(self, p=None, *args, **kwargs):
p : ArgumentParser
the argument parser to add the arguments to.
"""
pass

def __str__(self):
"""Return a representation of the `Sile`"""
Expand Down Expand Up @@ -744,7 +741,6 @@ def __exit__(self, type, value, traceback):

def close(self):
"""Will not close the file since this is passed by the user"""
pass


@set_module("sisl.io")
Expand Down Expand Up @@ -1483,7 +1479,7 @@ def sile_raise_write(self, ok=("w", "a")):
raise SileError(
(
"Writing to file not possible; allowed "
"modes={}, used mode={}".format(ok, self._mode)
f"modes={ok}, used mode={self._mode}"
),
self,
)
Expand All @@ -1499,36 +1495,3 @@ def sile_raise_read(self, ok=("r", "a")):
f"modes={ok}, used mode={self._mode}",
self,
)


@set_module("sisl.io")
class SileError(IOError):
"""Define an error object related to the Sile objects"""

def __init__(self, value, obj=None):
self.value = value
self.obj = obj

def __str__(self):
if self.obj:
return f"{self.value!s} in {self.obj!s}"
else:
return self.value


@set_module("sisl.io")
class SileWarning(SislWarning):
"""Warnings that informs users of things to be carefull about when using their retrieved data
These warnings should be issued whenever a read/write routine is unable to retrieve all information
but are non-influential in the sense that sisl is still able to perform the action.
"""

pass


@set_module("sisl.io")
class SileInfo(SislInfo):
"""Information for the user, this is hidden in a warning, but is not as severe so as to issue a warning."""

pass
10 changes: 8 additions & 2 deletions src/sisl/io/tbtrans/_cdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from sisl import Atom, Geometry, Lattice
from sisl._indices import indices
from sisl._internal import set_module
from sisl.messages import info, warn
from sisl.messages import deprecate, info, warn
from sisl.physics.distribution import fermi_dirac
from sisl.unit.siesta import unit_convert

Expand Down Expand Up @@ -88,7 +88,13 @@ def geometry(self):
"""The associated geometry from this file"""
return self.read_geometry()

geom = geometry
@property
def geom(self):
"""Same as `geometry`, but deprecated"""
deprecate(
f"{self.__class__.__name__}.geom is deprecated, please use '.geometry'."
)
return self.geometry

@property
@lru_cache(maxsize=1)
Expand Down
7 changes: 5 additions & 2 deletions src/sisl/io/tbtrans/pht.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@ def kT(self, elec):
return self._value("kT", self._elec(elec))[0] * Ry2eV


for _name in [
for _name in (
"chemical_potential",
"electron_temperature",
"kT",
"orbital_current",
"bond_current",
"vector_current",
"current",
"current_parameter",
"shot_noise",
"noise_power",
]:
):
setattr(phtncSilePHtrans, _name, None)
setattr(phtavncSilePHtrans, _name, None)

Expand Down
Loading

0 comments on commit d9acb0e

Please sign in to comment.