# This file is part of xrayutilities.
#
# xrayutilities is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# Copyright (c) 2010-2020, 2023 Dominik Kriegner <dominik.kriegner@gmail.com>
"""
xrayutilities utilities contains a conglomeration of useful functions
this part of utilities does not need the config class
"""
import abc
import numbers
import os.path
import re
import numpy
import scipy.constants
from .exception import InputError
try: # works in Python >3.4
ABC = abc.ABC
except AttributeError: # Python 2.7
ABC = abc.ABCMeta('ABC', (object, ), {'__slots__': ()})
__all__ = ['ABC', 'check_kwargs', 'en2lam', 'energies', 'energy',
'exchange_filepath', 'exchange_path', 'is_valid_variable_name',
'lam2en', 'makeNaturalName', 'wavelength']
energies = {
'CuKa1': 8047.82310,
'CuKa2': 8027.9117,
'CuKa12': 8041.18,
'CuKb': 8905.337,
'MoKa1': 17479.374,
'CoKa1': 6930.32,
'CoKa2': 6915.30}
# wavelength values from International Tables of Crystallography:
# Vol C, 2nd Ed. page 203
# CuKa1: 1.54059292(45) the value in bracket is the uncertainty
# CuKa2: 1.5444140(19)
# CuKa12: mixture 2:1 a1 and a2
# CuKb: 1.392246(14)
# MoKa1: 0.70931713(41)
# Xray data booklet:
# CoKa1
# CoKa2
[docs]
def lam2en(inp):
"""
converts the input wavelength in angstrom to an energy in eV
Parameters
----------
inp : float or str
wavelength in angstrom
Returns
-------
float
energy in eV
Examples
--------
>>> energy = lam2en(1.5406)
"""
# E(eV) = h*c/(e * lambda(A)) *1e10
inp = wavelength(inp)
c = scipy.constants
out = c.h * c.speed_of_light / (c.e * inp) * 1e10
return out
[docs]
def en2lam(inp):
"""
converts the input energy in eV to a wavelength in angstrom
Parameters
----------
inp : float or str
energy in eV
Returns
-------
float
wavlength in angstrom
Examples
--------
>>> wavelength = en2lam(8048)
"""
# lambda(A) = h*c/(e * E(eV)) *1e10
inp = energy(inp)
c = scipy.constants
out = c.h * c.speed_of_light / (c.e * inp) * 1e10
return out
[docs]
def energy(en):
"""
convert common energy names to energies in eV
so far this works with CuKa1, CuKa2, CuKa12, CuKb, MoKa1
Parameters
----------
en : float, array-like or str
energy either as scalar or array with value in eV, which will be
returned unchanged; or string with name of emission line
Returns
-------
float or array-like
energy in eV
"""
if isinstance(en, numbers.Number):
return numpy.double(en)
if isinstance(en, (numpy.ndarray, list, tuple)):
return numpy.asarray(en)
if isinstance(en, str):
return energies[en]
raise InputError("wrong type for argument en")
[docs]
def wavelength(wl):
"""
convert common energy names to energies in eV
so far this works with CuKa1, CuKa2, CuKa12, CuKb, MoKa1
Parameters
----------
wl : float, array-like or str
wavelength; If scalar or array the wavelength in angstrom will be
returned unchanged, string with emission name is converted to
wavelength
Returns
-------
float or array-like
wavelength in angstrom
"""
if isinstance(wl, numbers.Number):
return numpy.double(wl)
if isinstance(wl, (numpy.ndarray, list, tuple)):
return numpy.asarray(wl)
if isinstance(wl, str):
return en2lam(energies[wl])
raise InputError("wrong type for argument wavelength")
[docs]
def exchange_path(orig, new, keep=0, replace=None):
"""
function to exchange the root of a path with the option of keeping the
inner directory structure. This for example includes such a conversion
/dir_a/subdir/images/sample -> /home/user/data/images/sample
where the two innermost directory names are kept (keep=2), or equally
the three outer most are replaced (replace=3). One can either give keep,
or replace, with replace taking preference if both are given. Note that
replace=1 on Linux/Unix replaces only the root for absolute paths.
Parameters
----------
orig : str
original path which should be replaced by the new path
new : str
new path which should be used instead
keep : int, optional
number of inner most directory names which should be kept the same in
the output (default = 0)
replace : int, optional
number of outer most directory names which should be replaced in the
output (default = None)
Returns
-------
str
directory path string
Examples
--------
>>> p = exchange_path('/dir_a/subdir/img/sam', '/home/user/data', keep=2)\
# you get '/home/user/data/img/sam'
"""
subdirs = []
o = orig
if replace is None:
for _ in range(keep):
o, s = os.path.split(o)
subdirs.append(s)
out = new
subdirs.reverse()
for s in subdirs:
out = os.path.join(out, s)
else:
while True:
o, s = os.path.split(o)
if not s:
subdirs.append(o)
break
if not o:
subdirs.append(s)
break
subdirs.append(s)
subdirs.reverse()
out = new
for s in subdirs[replace:]:
out = os.path.join(out, s)
return out
[docs]
def exchange_filepath(orig, new, keep=0, replace=None):
"""
function to exchange the root of a filename with the option of keeping the
inner directory structure. This for example includes such a conversion
/dir_a/subdir/sample/file.txt -> /home/user/data/sample/file.txt
where the innermost directory name is kept (keep=1), or equally
the three outer most are replaced (replace=3). One can either give keep,
or replace, with replace taking preference if both are given. Note that
replace=1 on Linux/Unix replaces only the root for absolute paths.
Parameters
----------
orig : str
original filename which should have its data root replaced
new : str
new path which should be used instead
keep : int, optional
number of inner most directory names which should be kept the same in
the output (default = 0)
replace : int, optional
number of outer most directory names which should be replaced in the
output (default = None)
Returns
-------
str
filename string
Examples
--------
>>> newfile = exchange_filepath('/dir_a/subdir/sam/file.txt', '/data', 1)\
# you get '/data/sam/file.txt'
"""
if new:
if replace is None:
return exchange_path(orig, new, keep+1)
return exchange_path(orig, new, replace=replace)
return orig
[docs]
def makeNaturalName(name, check=False):
ret = re.sub('[^0-9a-zA-Z]', '_', name.strip())
isvalid = is_valid_variable_name(ret)
if not check or isvalid:
return ret
raise ValueError(f"'{ret}' is not valid variable name")
[docs]
def is_valid_variable_name(name):
return name.isidentifier()
[docs]
def check_kwargs(kwargs, valid_kwargs, identifier):
"""
Raises an TypeError if kwargs included a key which is not in valid_kwargs.
Parameters
----------
kwargs : dict
keyword arguments dictionary
valid_kwargs : dict
dictionary with valid keyword arguments and their description
identifier : str
string to identifier the caller of this function
"""
desc = ', '.join([f"'{k}': {d}" for k, d in valid_kwargs.items()])
for k in kwargs:
if k not in valid_kwargs:
raise TypeError("%s: unknown keyword argument ('%s') given; "
"allowed are %s" % (identifier, k, desc))