Skip to content

Commit

Permalink
added doc for env-vars, fixed units, added siesta-codata2018
Browse files Browse the repository at this point in the history
- Documentation of sisl environment variables.

- Added typing to sisl._environ

- Fixed errors in UnitParser
  It should now also be much faster since the routines has been
  cleaned for superfluous code.

  - now the conversion is only getting the exact value
    of the requested value. This allows one to compare between
    different versions (previously it always converted to the
    default unit, which can create corner cases when comparing
    units between different conventions).
  - fixed requesting a single unit (just get the default SI value)

  - added test for single unit getting

Signed-off-by: Nick Papior <[email protected]>
  • Loading branch information
zerothi committed Sep 27, 2023
1 parent b02a69f commit cea8b10
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 80 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ we hit release version 1.0.0.
## [0.14.0] - YYYY-MM-DD

### Added
- added SISL_UNIT_SIESTA to select between legacy or codata2018 units (since Siesta 5)
New default is codata2018, may create inconsistencies until Siesta 5 is widely adopted.
- added --remove to sgeom for removing single atoms
- added a EllipticalCylinder as a new shape
- added basis-enthalpy to the stdoutSiestaSile.read_energy routine
Expand Down
65 changes: 65 additions & 0 deletions docs/environment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.. _environment:

Environment variables
=====================

sisl understands some environment variables that may be used to tweak, or change
the default behaviour of sisl.

Here we list the different options:


``SISL_NUM_PROCS = 1``
Default the number of processors used in parallel calculations.
Currently only certain Brillouin zone operations has this enabled.

Please test this for your machine before relying on it giving a lot
of performance. Especially in conjunction with the ``OMP_NUM_THREADS``
flag for OpenMP in linear algebra libraries.
Benchmark and see if it actually improves (certain combinations will
severly hurt performance).

``SISL_VIZ_AUTOLOAD == false``
whether or not to autoload the visualization module.
The visualization module imports many dependent modules.
If you run small scripts that does not use the `sisl.viz` module, then
it is recommended to keep this to be false.

``SISL_SHOW_PROGRESS = false``
Certain sisl routines has a builtin progress bar. This variable can default
whether or not those will be shown. It can be nice for *slow* brillouinzone calculations
to see if progress is actually being made.

``SISL_IO_DEFAULT = ''``
The default IO methods `sisl.get_sile` will select files with this file-endings.
For instance there are many ``stdout`` file types (for each DFT code).
Setting this to ``Siesta`` would force all files to first search for Siesta file
endings (see `sisl.io` for class names).

``SISL_TMP = '.sisl_tmp'``
certain internal methods of sisl will use a temporary folder for storing data.
The default is a new folder in the currently executed directory.

``SISL_FILES_TESTS``
Full path to a folder containing tests files. Primarily used for developers.

``SISL_CONFIGDIR = $HOME/.config/sisl``
where certain configuration files should be stored.

Currently not in use.


Code specific environment variables
-----------------------------------

Siesta
^^^^^^

``SISL_UNIT_SIESTA = codata2018 | legacy``
determine the default units for Siesta files.

Since Siesta 5.0, the default units are updated to follow
the CODATA 2018 values. This means that quite a bit of
results changed. This will force the internal variables
to be consistent with this.

1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ of places to search/ask for answers:

installation
tutorials.rst
environment
scripts/scripts
nodes/nodes_intro

Expand Down
10 changes: 6 additions & 4 deletions src/sisl/_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
# 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 os
from collections.abc import Callable
from contextlib import contextmanager
from pathlib import Path
from textwrap import dedent
from typing import Any

__all__ = ["register_environ_variable", "get_environ_variable", "sisl_environ"]

Expand Down Expand Up @@ -33,9 +35,9 @@ def sisl_environ(**environ):
SISL_ENVIRON[key]["value"] = old[key]


def register_environ_variable(name, default,
description=None,
process=None):
def register_environ_variable(name: str , default: Any,
description: str=None,
process: Callable[[Any], Any]=None):
"""Register a new global sisl environment variable.
Parameters
Expand Down Expand Up @@ -75,7 +77,7 @@ def process(arg):
}


def get_environ_variable(name):
def get_environ_variable(name: str):
""" Gets the value of a registered environment variable.
Parameters
Expand Down
10 changes: 8 additions & 2 deletions src/sisl/io/siesta/tests/test_eig.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@
from sisl.io.siesta.eig import *
from sisl.io.siesta.fdf import *


pytestmark = [pytest.mark.io, pytest.mark.siesta]
_dir = osp.join("sisl", "io", "siesta")


def _convert45(unit):
""" Convert from legacy units to CODATA2018 """
from sisl.unit.siesta import units, units_legacy
return units(unit) / units_legacy(unit)


def test_si_pdos_kgrid_eig(sisl_files):
f = sisl_files(_dir, "si_pdos_kgrid.EIG")
eig = eigSileSiesta(f).read_data()

# nspin, nk, nb
assert np.all(eig.shape == (1, 32, 26))


def test_si_pdos_kgrid_eig_ArgumentParser(sisl_files, sisl_tmp):
pytest.importorskip("matplotlib", reason="matplotlib not available")
png = sisl_tmp("si_pdos_kgrid.EIG.png", _dir)
Expand Down Expand Up @@ -83,4 +89,4 @@ def test_soc_pt2_xx_eig_fermi_level(sisl_files):
# vs. siesta we have to make this.
# once https://gitlab.com/siesta-project/siesta/-/merge_requests/30
# is merged
assert ef == pytest.approx(ef2, abs=1e-5)
assert ef * _convert45("eV") == pytest.approx(ef2, abs=1e-5)
1 change: 1 addition & 0 deletions src/sisl/tests/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1453,6 +1453,7 @@ def test_geometry_ase_new_to(self):
from_ase = gr.new(to_ase)
assert gr.equal(from_ase, R=False)

@pytest.mark.xfail(reason="pymatgen backconversion sets nsc=[3, 3, 3], we need to figure this out")
def test_geometry_pymatgen_to(self):
pytest.importorskip("pymatgen", reason="pymatgen not available")
gr = sisl_geom.graphene()
Expand Down
63 changes: 26 additions & 37 deletions src/sisl/unit/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
'atu': 2.4188843265857e-17},
'energy': {'DEFAULT': 'eV',
'J': 1.0,
'kJ': 1.e3,
'erg': 1e-07,
'K': 1.380649e-23,
'eV': 1.602176634e-19,
Expand All @@ -55,7 +56,7 @@


@set_module("sisl.unit")
def unit_group(unit, tbl=None):
def unit_group(unit, tbl=unit_table):
""" The group of units that `unit` belong to
Parameters
Expand All @@ -72,18 +73,14 @@ def unit_group(unit, tbl=None):
>>> unit_group("eV")
"energy"
"""
if tbl is None:
global unit_table
tbl = unit_table

for k in tbl:
if unit in tbl[k]:
return k
raise ValueError(f"The unit ""{unit!s}"" could not be located in the table.")


@set_module("sisl.unit")
def unit_default(group, tbl=None):
def unit_default(group, tbl=unit_table):
""" The default unit of the unit group `group`.
Parameters
Expand All @@ -98,10 +95,6 @@ def unit_default(group, tbl=None):
>>> unit_default("energy")
"eV"
"""
if tbl is None:
global unit_table
tbl = unit_table

for k in tbl:
if group == k:
return tbl[k]["DEFAULT"]
Expand All @@ -110,7 +103,7 @@ def unit_default(group, tbl=None):


@set_module("sisl.unit")
def unit_convert(fr, to, opts=None, tbl=None):
def unit_convert(fr, to, opts=None, tbl=unit_table):
""" Factor that takes `fr` to the units of `to`
Parameters
Expand All @@ -131,9 +124,6 @@ def unit_convert(fr, to, opts=None, tbl=None):
>>> unit_convert("eV","J")
1.60217733e-19
"""
if tbl is None:
global unit_table
tbl = unit_table
if opts is None:
opts = dict()

Expand Down Expand Up @@ -186,19 +176,17 @@ class UnitParser:
unit_table : dict
a table with the units parsable by the class
"""
__slots__ = ["_table", "_p_left", "_left", "_p_right", "_right"]
__slots__ = ("_table", "_p_left", "_left", "_p_right", "_right")

def __init__(self, table):
self._table = table

def convert(fr, to):
def value(unit):
tbl = self._table
for k in tbl:
if fr in tbl[k]:
if to in tbl[k]:
return tbl[k][fr] / tbl[k][to]
break
raise ValueError(f"The unit conversion is not from the same group: {fr} to {to}!")
if unit in tbl[k]:
return tbl[k][unit]
raise ValueError(f"The unit conversion did not contain unit {unit}!")

def group(unit):
tbl = self._table
Expand All @@ -215,41 +203,43 @@ def default(group):
return k["DEFAULT"]

self._left = []
self._p_left = self.create_parser(convert, default, group, self._left)
self._p_left = self.create_parser(value, default, group, self._left)
self._right = []
self._p_right = self.create_parser(convert, default, group, self._right)
self._p_right = self.create_parser(value, default, group, self._right)

@staticmethod
def create_parser(convert, default, group, group_table=None):
def create_parser(value, default, group, group_table=None):
""" Routine to internally create a parser with specified unit_convert, unit_default and unit_group routines """

# Any length of characters will be used as a word.
if group_table is None:
def _convert(t):
return convert(t[0], default(group(t[0])))
def _value(t):
return value(t[0])

def _float(t):
return float(t[0])

else:
def _convert(t):
def _value(t):
group_table.append(group(t[0]))
return convert(t[0], default(group_table[-1]))
return value(t[0])

def _float(t):
f = float(t[0])
group_table.append(f) # append nothing
return f

# The unit extractor
unit = pp.Word(pp.alphas).setParseAction(_convert)
unit = pp.Word(pp.alphas).setParseAction(_value)

integer = pp.Word(pp.nums)
plusorminus = pp.oneOf("+ -")
point = pp.Literal(".")
e = pp.CaselessLiteral("E")
sign_integer = pp.Combine(pp.Optional(plusorminus) + integer)
exponent = pp.Combine(e + sign_integer)
sign_integer = pp.Combine(pp.Optional(plusorminus) + integer)
exponent = pp.Combine(e + sign_integer)
number = pp.Or([pp.Combine(point + integer + pp.Optional(exponent)), # .[0-9][E+-[0-9]]
pp.Combine(integer + pp.Optional(point + pp.Optional(integer)) + pp.Optional(exponent))] # [0-9].[0-9][E+-[0-9]]
).setParseAction(_float)
Expand Down Expand Up @@ -343,14 +333,13 @@ def _convert(self, A, B):
conv_A = self._p_left.parseString(A)[0]
conv_B = self._p_right.parseString(B)[0]
if not self.same_group(self._left, self._right):
# Ensure lists are cleaned (in case the user catches stuff
left = list(self._left)
right = list(self._right)
self._left = []
self._right = []
self._left.clear()
self._right.clear()
raise ValueError(f"The unit conversion is not from the same group: {left} to {right}!")
self._left = []
self._right = []
self._left.clear()
self._right.clear()
return conv_A / conv_B

def convert(self, *units):
Expand Down Expand Up @@ -387,11 +376,11 @@ def convert(self, *units):

elif len(units) == 1:
# to default
conv = self._p_left(units[0])
self._left = []
conv = self._p_left.parseString(units[0])[0]
self._left.clear()
return conv

return tuple(self._convert(units[i], units[i + 1]) for i in range(len(units) - 1))
return tuple(self._convert(A, B) for A, B in zip(units[:-1], units[1:]))

def __call__(self, *units):
return self.convert(*units)
Expand Down
Loading

0 comments on commit cea8b10

Please sign in to comment.