Skip to content

simple.utils

simple.utils.OptionalArg module-attribute

OptionalArg = object()

simple.utils.REATTR module-attribute

REATTR = '(?:[ ]*([+-]?[0-9]*[.]?[0-9]*(?:[Ee]?[+-]?[0-9]+)?)[ ]*|(.*))'

simple.utils.REINDEX module-attribute

REINDEX = '([-]?[0-9]*)[:]([-]?[0-9]*)[:]?([-]?[0-9]*)|([-]?[0-9]+)'

simple.utils.SimpleLogger module-attribute

SimpleLogger = getLogger('SIMPLE')

simple.utils.UNITS module-attribute

UNITS = dict(mass=['mass', 'massfrac', 'wt', 'wt%'], mole=['mole', 'moles', 'mol', 'molfrac'])

A dictionary containing the names associated with different unit types

Current unit types are:

  • mass that represents data being stored in a mass unit or as mass fractions.
  • mole that represents data being stored in moles or as mole fractions.

simple.utils.mask_eval module-attribute

mask_eval = MaskEval()

simple.utils.simple_eval module-attribute

simple_eval = AttrEval()

simple.utils.AttrEval

AttrEval()
Source code in simple/utils.py
968
969
def __init__(self):
    self.ab_evalstrings = []

ab_evalstrings instance-attribute

ab_evalstrings = []

add_ab_evaluator

add_ab_evaluator(opstr, operator)
Source code in simple/utils.py
971
972
def add_ab_evaluator(self, opstr, operator):
    self.ab_evalstrings.append((opstr, f'{REATTR}{opstr}{REATTR}', operator))

eval

eval(item, where, **where_kwargs)
Source code in simple/utils.py
1007
1008
1009
def eval(self, item, where, **where_kwargs):
    evaluate = self.parse_where(where)
    return evaluate(item, where_kwargs)

parse_where

parse_where(where)
Source code in simple/utils.py
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
def parse_where(self, where):
    eval = BoolEvaluator()

    if type(where) is not str:
        eval.add(None, where)
        return eval

    if "&" in where and "|" in where:
        raise ValueError('Where strings cannot contain both & and |')
    elif "|" in where:
        evalstrings = where.split('|')
        eval._and = False
    else:
        evalstrings = where.split('&')

    for evalstr in evalstrings:
        evalstr = evalstr.strip()
        if len(evalstr) == 0: continue

        for opstr, regex, opfunc in self.ab_evalstrings:
            # The first check significantly speeds up the evaluation
            if (opstr in evalstr) and (m := re.fullmatch(regex, evalstr.strip())):
                a_number, a_string, b_number, b_string = m.groups()
                eval.add(opfunc, EvalArg(a_number, a_string), EvalArg(b_number, b_string))
                break
        else:
            if (m := re.fullmatch(REATTR, evalstr.strip())):
                a_number, a_string = m.groups()
                eval.add(None, EvalArg(a_number, a_string))
            else:
                raise ValueError(f'Unable to parse condition "{evalstr}"')
    return eval

simple.utils.BoolEvaluator

BoolEvaluator()
Source code in simple/utils.py
893
894
895
def __init__(self):
    self._evaluators = []
    self._and = True

add

add(operator, *args)
Source code in simple/utils.py
897
898
def add(self, operator, *args):
    self._evaluators.append((operator, args))

eval

eval(item, kwargs=None)
Source code in simple/utils.py
925
926
def eval(self, item, kwargs=None):
    return self.__call__(item, kwargs)

simple.utils.DefaultKwargsWrapper

DefaultKwargsWrapper(func, default_kwargs, inherits=False)
Source code in simple/utils.py
173
174
175
176
def __init__(self, func, default_kwargs, inherits=False):
    self._func = func
    self._default_kwargs = default_kwargs
    self._inherits = inherits

default_kwargs property

default_kwargs

add_shortcut

add_shortcut(name, inherits=True, **kwargs)
Source code in simple/utils.py
186
187
188
def add_shortcut(self, name, inherits=True, **kwargs):
    setattr(self, name, DefaultKwargsWrapper(self, kwargs, inherits))
    return getattr(self, name)

update_default_kwargs

update_default_kwargs(**kwargs)
Source code in simple/utils.py
202
203
def update_default_kwargs(self, **kwargs):
    self._default_kwargs.update(kwargs)

simple.utils.Element

Bases: str

RE class-attribute instance-attribute

RE = '([a-zA-Z]{1,2})([*_: ][^/]*)?'

latex

latex(dollar=True)

Returns a latex representation of the string e.g. Pd -> \(\mathrm{Pd}\)

Parameters:

  • dollar (bool, default: True ) –

    Whether to include the bracketing $ signs.

Source code in simple/utils.py
451
452
453
454
455
456
457
458
459
460
461
462
463
def latex(self, dollar=True):
    """
    Returns a latex representation of the string e.g. Pd -> $\mathrm{Pd}$

    Args:
        dollar (bool): Whether to include the bracketing ``$`` signs.

    """
    string = fr"\mathrm{{{self.symbol}{self.suffix}}}"
    if dollar:
        return f"${string}$"
    else:
        return string

without_suffix

without_suffix()

Return a new element string without the suffix.

Source code in simple/utils.py
465
466
467
468
469
def without_suffix(self):
    """
    Return a new element string without the suffix.
    """
    return self._new_(self.symbol, '')

simple.utils.EndlessList

Bases: list

A subclass of list that where the index will never go out of bounds. If a requested index is out of bounds, it will cycle around to the start of the list.

Examples:

>>> ls = simple.plot.Endlesslist(["a", "b", "c"])
>>> ls[3]
"a"

simple.utils.EvalArg

EvalArg(number, string)
Source code in simple/utils.py
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
def __init__(self, number, string):
    if number is not None:
        if '.' in number:
            v = float(number.strip())
        else:
            v = int(number)
        self.value = v
        self.is_attr = False
        self.is_kwarg = False
    else:
        string = string.strip()
        if string[0] == '.':
            is_attr = True
            string = string[1:].strip()
        else:
            is_attr = False
        if string[0] == '{' and string[-1] == '}':
            is_kwarg = True
            string = string[1:-1].strip()
        else:
            is_kwarg = False

        self.value = string
        self.is_attr = is_attr
        self.is_kwarg = is_kwarg
        if self.is_attr is False and self.is_kwarg is False:
            if string == 'True':
                self.value = True
            elif string == 'False':
                self.value = False
            elif string == 'None':
                self.value = None

NoAttr class-attribute instance-attribute

NoAttr = object()

is_attr instance-attribute

is_attr = False

is_kwarg instance-attribute

is_kwarg = False

value instance-attribute

value = v

NoAttributeError

Bases: AttributeError

simple.utils.Isotope

Isotope(string, without_suffix=False)

Bases: str

A subclass of string representing an isotope using the format <element symbol>-<mass number><suffix> e.g. Pd-105.

The order of the element symbol and mass number in string is not important, but they must proceed the suffix. The element symbol and mass number can optionally be seperated by -. The case of the element symbol is not considered.

Parameters:

  • string (str) –

    A string element symbol and a mass number.

  • without_suffix (bool, default: False ) –

    If True the suffix part of the string is ignored.

Attributes:

  • element (str) –

    The element symbol of the isotope

  • mass (str) –

    The mass number of the isotope

  • suffix (str) –

    The suffix of the isotope

Raises:

  • ValueError

    If string does not represent a valid isotope.

Source code in simple/utils.py
511
512
513
def __init__(self, string, without_suffix=False):
    # Never called. Just for the docstring.
    super(Isotope, self).__init__()

RE class-attribute instance-attribute

RE = '((([a-zA-Z]{1,2})[-]?([0-9]{1,3}))|(([0-9]{1,3})[-]?([a-zA-Z]{1,2})))([*_: ].*)?'

latex

latex(dollar=True)

Returns a latex representation of the string e.g. Pd-105 -> \({}^{105}\mathrm{Pd}\)

Parameters:

  • dollar (bool, default: True ) –

    Whether to include the bracketing $ signs.

Source code in simple/utils.py
524
525
526
527
528
529
530
531
532
533
534
535
536
def latex(self, dollar=True):
    """
    Returns a latex representation of the string e.g. Pd-105 -> ${}^{105}\mathrm{Pd}$

    Args:
        dollar (bool): Whether to include the bracketing ``$`` signs.

    """
    string = fr"{{}}^{{{self.mass}}}\mathrm{{{self.element}}}"
    if dollar:
        return f"${string}$"
    else:
        return string

without_suffix

without_suffix()

Return a new isotope string without the suffix.

Source code in simple/utils.py
538
539
540
541
542
def without_suffix(self):
    """
    Return a new isotope string without the suffix.
    """
    return self._new_(self.mass, self.element.without_suffix())

simple.utils.MaskEval

MaskEval()
Source code in simple/utils.py
1015
1016
def __init__(self):
    self.ab_evalstrings = []

ab_evalstrings instance-attribute

ab_evalstrings = []

add_ab_evaluator

add_ab_evaluator(opstr, operator)
Source code in simple/utils.py
1018
1019
def add_ab_evaluator(self, opstr, operator):
    self.ab_evalstrings.append((opstr, f'{REATTR}{opstr}{REATTR}', operator))

eval

eval(item, mask, shape, **mask_kwargs)
Source code in simple/utils.py
1064
1065
1066
def eval(self, item, mask, shape, **mask_kwargs):
    evaluate = self.parse_mask(mask)
    return evaluate(item, shape, mask_kwargs)

parse_mask

parse_mask(mask)
Source code in simple/utils.py
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
def parse_mask(self, mask):
    eval = MaskEvaluator()

    if type(mask) is not str:
        eval.add(None, mask)
        return eval

    if "&" in mask and "|" in mask:
        raise ValueError('Mask string cannot contain & and |')
    elif "|" in mask:
        evalstrings = mask.split('|')
        eval._and = False
    else:
        evalstrings = mask.split('&')

    for evalstr in evalstrings:
        evalstr = evalstr.strip()
        if len(evalstr) == 0:
            pass

        elif (m := re.fullmatch(REINDEX, evalstr)):
            start, stop, step, index = m.groups()
            if index is not None:
                eval.add(None, int(index))
            else:
                eval.add(None, slice(int(start) if start.strip() else None,
                                                    int(stop) if stop.strip() else None,
                                                    int(step) if step.strip() else None))
        else:
            for opstr, regex, opfunc in self.ab_evalstrings:
                # The first check significantly speeds up the evaluation
                if (opstr in evalstr) and (m := re.fullmatch(regex, evalstr.strip())):
                    a_number, a_string, b_number, b_string = m.groups()
                    eval.add(opfunc, EvalArg(a_number, a_string), EvalArg(b_number, b_string))
                    break
            else:
                if (m := re.fullmatch(REATTR, evalstr.strip())):
                    a_number, a_string = m.groups()
                    eval.add(None, EvalArg(a_number, a_string))
                else:
                    raise ValueError(f'Unable to parse condition "{evalstr}"')
    return eval

simple.utils.MaskEvaluator

MaskEvaluator()

Bases: BoolEvaluator

Source code in simple/utils.py
893
894
895
def __init__(self):
    self._evaluators = []
    self._and = True

simple.utils.NamedDict

NamedDict(*args, **kwargs)

Bases: dict

A subclass of a normal dict where item in the dictionary can also be accessed as attributes.

Examples:

>>> nd = simple.utils.NamedDict({'a': 1, 'b': 2, 'c': 3})
>>> nd.a
1
Source code in simple/utils.py
 99
100
def __init__(self, *args, **kwargs):
    self.update(*args, **kwargs)

setdefault

setdefault(key, default_value)
Source code in simple/utils.py
112
113
114
115
116
def setdefault(self, key, default_value):
    if key not in self:
        self[key] = default_value

    return self[key]

update

update(*args, **kwargs)
Source code in simple/utils.py
108
109
110
def update(self, *args, **kwargs):
    for k, v in dict(*args, **kwargs).items():
        self[k] = v

simple.utils.Ratio

Bases: str

A subclass of string representing a ratio of two isotopes using the format <numer>/<denom> e.g. Pd-108/Pd-105.

Parameters:

  • string (str) –

    A string consisting of two isotope seperated by a /.

  • without_suffix (bool) –

    If True the suffix part of the numerator and denominator`isotopes is ignored.

Attributes:

  • numer (str) –

    The numerator isotope

  • mass (str) –

    The denominator isotope

Raises:

  • ValueError

    If string does not represent a isotope ratio.

latex

latex(dollar=True)

Returns a latex representation of the string e.g. Pd-108/Pd-105 -> \({}^{108}\mathrm{Pd}/{}^{105}\mathrm{Pd}\)

Parameters:

  • dollar (bool, default: True ) –

    Whether to include the bracketing $ signs.

Source code in simple/utils.py
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
def latex(self, dollar=True):
    """
    Returns a latex representation of the string e.g. Pd-108/Pd-105 -> ${}^{108}\mathrm{Pd}/{}^{105}\mathrm{Pd}$

    Args:
        dollar (bool): Whether to include the bracketing ``$`` signs.
    """
    if type(self.numer) is str:
        numer = self.numer
    else:
        numer = self.numer.latex(dollar=dollar)
    if type(self.denom) is str:
        denom = self.denom
    else:
        denom = self.denom.latex(dollar=dollar)

    return f"{numer}/{denom}"

without_suffix

without_suffix()

Return a new isotope string without the suffix.

Source code in simple/utils.py
593
594
595
596
597
def without_suffix(self):
    """
    Return a new isotope string without the suffix.
    """
    return self._new_(self.numer.without_suffix(), self.denom.without_suffix())

simple.utils.add_shortcut

add_shortcut(name, inherits=True, **shortcut_kwargs)
Source code in simple/utils.py
256
257
258
259
260
261
262
263
264
def add_shortcut(name, inherits = True, **shortcut_kwargs):
    def inner(func):
        if not isinstance(func, DefaultKwargsWrapper):
            setattr(func, name, DefaultKwargsWrapper(func, shortcut_kwargs, False if inherits is True else inherits))
        else:
            func.add_shortcut(name, inherits = inherits, **shortcut_kwargs)

        return func
    return inner

simple.utils.asarray

asarray(values, dtype=None, saving=False)

Convert data to a numpy array.

If data is a string or a sequence of strings and saving=False, either a single string or a tuple of string will be returned. If saving is True the values will be converted to an array with a byte dtype. This ensures values are compatible with the hdf5 library.

Arrays with a bytes dtype will automatically be converted to the str dtype. If saving is False then this values will be converted to either a string or a tuple of strings (see above).

Parameters:

  • values

    An values like object.

  • dtype

    The data type of the returned values.

  • saving

    Should be True is the data is to be saved in a hdf5 file.

Source code in simple/utils.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def asarray(values, dtype=None, saving=False):
    """
    Convert ``data`` to a numpy array.

    If ``data`` is a string or a sequence of strings and ``saving=False``, either a single string or a tuple
    of string will be returned. If ``saving`` is ``True`` the values will be converted to an array with a byte dtype.
    This ensures values are compatible with the hdf5 library.

    Arrays with a ``bytes`` dtype will automatically be converted to the ``str`` dtype. If ``saving`` is ``False`` then
    this values will be converted to either a string or a tuple of strings (see above).

    Args:
        values (): An values like object.
        dtype (): The data type of the returned values.
        saving (): Should be ``True`` is the data is to be saved in a hdf5 file.

    """
    values = np.asarray(values, dtype=dtype)

    if values.dtype.type is np.bytes_:
        values = values.astype(np.str_)

    if not saving and values.dtype.type is np.str_:
        values = values.tolist()
        if type(values) is list:
            values = tuple(values)

    if saving and values.dtype.type is np.str_:
        values = values.astype(np.bytes_)

    return values

simple.utils.aselement

aselement(string, without_suffix=False, allow_invalid=False)

Returns a Element representing an element symbol.

The returned element format is the capitalised element symbol followed by the suffix, if present. E.g. Pd-104* where * is the suffix.

The case of the element symbol is not considered.

Parameters:

  • string (str) –

    A string containing an element symbol.

  • without_suffix

    If True the suffix part of the string is ignored.

  • allow_invalid

    If False, and string cannot be parsed into an element string, an exception is raised. If True then string.strip() is returned instead.

Examples:

>>> ele = simple.asisotope("pd"); ele
"Pd"
Source code in simple/utils.py
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
def aselement(string, without_suffix=False, allow_invalid=False):
    """
    Returns a [``Element``][simple.utils.Element] representing an element symbol.

    The returned element format is the capitalised element
    symbol followed by the suffix, if present. E.g. ``Pd-104*`` where
    ``*`` is the suffix.

    The case of the element symbol is not considered.

    Args:
        string (str): A string containing an element symbol.
        without_suffix (): If ``True`` the suffix part of the string is ignored.
        allow_invalid ():  If ``False``, and ``string`` cannot be parsed into an element string, an exception is
            raised. If ``True`` then ``string.strip()`` is returned instead.

    Examples:
        >>> ele = simple.asisotope("pd"); ele
        "Pd"


    """
    if type(string) is Element:
        if without_suffix:
            return string.without_suffix()
        else:
            return string
    elif isinstance(string, str):
        string = string.strip()
    else:
        raise TypeError(f'``string`` must a str not {type(string)}')

    try:
        return Element(string, without_suffix=without_suffix)
    except ValueError:
        if allow_invalid:
            return string
        else:
            raise

simple.utils.aselements

aselements(strings, without_suffix=False, allow_invalid=False)

Returns a tuple of Element strings where each string represents an element symbol.

Parameters:

  • strings

    Can either be a string with element symbol seperated by a , or a sequence of strings.

  • without_suffix

    If True the suffix part of each isotope string is ignored.

  • allow_invalid

    If False, and a string cannot be parsed into an isotope string, an exception is raised. If True then string.strip() is returned instead.

Examples:

>>> simple.asisotopes('ru, pd, cd')
('Ru', 'Pd', 'Cd')
>>> simple.asisotopes(['ru', 'pd', 'cd'])
('Ru', 'Pd', 'Cd')
Source code in simple/utils.py
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
def aselements(strings, without_suffix=False, allow_invalid=False):
    """
    Returns a tuple of [``Element``][simple.utils.Element] strings where each string represents an element symbol.

    Args:
        strings (): Can either be a string with element symbol seperated by a ``,`` or a sequence of strings.
        without_suffix (): If ``True`` the suffix part of each isotope string is ignored.
        allow_invalid ():  If ``False``, and a string cannot be parsed into an isotope string, an exception is
            raised. If ``True`` then ``string.strip()`` is returned instead.

    Examples:
        >>> simple.asisotopes('ru, pd, cd')
        ('Ru', 'Pd', 'Cd')

        >>> simple.asisotopes(['ru', 'pd', 'cd'])
        ('Ru', 'Pd', 'Cd')
    """
    if type(strings) is str:
        strings = [s for s in strings.split(',')]

    return tuple(aselement(string, without_suffix=without_suffix, allow_invalid=allow_invalid) for string in strings)

simple.utils.asisolist

asisolist(isolist, without_suffix=False, allow_invalid=False)

Return a dictionary consisting of an isotope key mapped to a tuple of isotopes that should make up the key isotope.

If isolist is list or tuple of keys then each key will be mapped only to itself.

Parameters:

  • isolist

    Either a dictionary mapping a single isotope to a list of isotopes or a sequence of isotopes that will be mapped to themselfs.

  • without_suffix

    If True the suffix part of each isotope string is ignored.

  • allow_invalid

    If True invalid isotopes string are allowed. If False they will instead raise an exception.

Source code in simple/utils.py
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
def asisolist(isolist, without_suffix=False, allow_invalid=False):
    """
    Return a dictionary consisting of an isotope key mapped to a tuple of isotopes that should make up the
    key isotope.

    If ``isolist`` is list or tuple of keys then each key will be mapped only to itself.

    Args:
        isolist (): Either a dictionary mapping a single isotope to a list of isotopes or a sequence of isotopes that
            will be mapped to themselfs.
        without_suffix (): If ``True`` the suffix part of each isotope string is ignored.
        allow_invalid (): If ``True`` invalid isotopes string are allowed. If ``False`` they will instead raise
            an exception.
    """
    if type(isolist) is not dict:
        isolist = asisotopes(isolist, without_suffix=without_suffix, allow_invalid=allow_invalid)
        return {iso: (iso,) for iso in isolist}
    else:
        return {asisotope(k, without_suffix=without_suffix, allow_invalid=allow_invalid):
                asisotopes(v, without_suffix=without_suffix, allow_invalid=allow_invalid)
                for k,v in isolist.items()}

simple.utils.asisotope

asisotope(string, without_suffix=False, allow_invalid=False)

Returns a Isotope representing an isotope.

The returned isotope format is the capitalised element symbol followed by a dash followed by the mass number followed by the suffix, if present. E.g. Pd-104* where * is the suffix.

The order of the element symbol and mass number in string is not important, but they must proceed the suffix. The element symbol and mass number may be seperated by -. The case of the element symbol is not considered.

Parameters:

  • string (str) –

    A string element symbol and a mass number.

  • without_suffix

    If True the suffix part of the isotope string is ignored.

  • allow_invalid

    If False, and string cannot be parsed into an isotope string, an exception is raised. If True then string.strip() is returned instead.

Examples:

>>> iso = simple.asisotope("104pd"); iso # pd104, 104-Pd etc are also valid
"Pd-104"
>>> iso.symbol, iso.mass
"Pd", "104"
Source code in simple/utils.py
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
def asisotope(string, without_suffix=False, allow_invalid=False):
    """
    Returns a [``Isotope``][simple.utils.Isotope] representing an isotope.

    The returned isotope format is the capitalised element
    symbol followed by a dash followed by the mass number followed by the suffix, if present. E.g. ``Pd-104*`` where
    ``*`` is the suffix.

    The order of the element symbol and mass number in ``string`` is not important, but they must proceed the suffix.
    The element symbol and mass number may be seperated by ``-``. The case of the element symbol is not
    considered.

    Args:
        string (str): A string element symbol and a mass number.
        without_suffix (): If ``True`` the suffix part of the isotope string is ignored.
        allow_invalid ():  If ``False``, and ``string`` cannot be parsed into an isotope string, an exception is
            raised. If ``True`` then ``string.strip()`` is returned instead.

    Examples:
        >>> iso = simple.asisotope("104pd"); iso # pd104, 104-Pd etc are also valid
        "Pd-104"
        >>> iso.symbol, iso.mass
        "Pd", "104"

    """
    if type(string) is Isotope:
        if without_suffix:
            return string.without_suffix()
        else:
            return string
    elif isinstance(string, str):
        string = string.strip()
    else:
        raise TypeError(f'``string`` must a str not {type(string)}')

    try:
        return Isotope(string, without_suffix=without_suffix)
    except ValueError:
        if allow_invalid:
            return string
        else:
            raise

simple.utils.asisotopes

asisotopes(strings, without_suffix=False, allow_invalid=False)

Returns a tuple of Isotope strings where each string represents an isotope.

Parameters:

  • strings

    Can either be a string with isotopes seperated by a , or a sequence of strings.

  • without_suffix

    If True the suffix part of each isotope string is ignored.

  • allow_invalid

    If False, and a string cannot be parsed into an isotope string, an exception is raised. If True then string.strip() is returned instead.

Examples:

>>> simple.asisotopes('104pd, pd105, 106-Pd')
('Pd-104', 'Pd-105, 106-Pd')
>>> simple.asisotopes(['104pd', 'pd105', '106-Pd'])
('Pd-104', 'Pd-105, 106-Pd')
Source code in simple/utils.py
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
def asisotopes(strings, without_suffix=False, allow_invalid=False):
    """
    Returns a tuple of [``Isotope``][simple.utils.Isotope] strings where each string represents an isotope.

    Args:
        strings (): Can either be a string with isotopes seperated by a ``,`` or a sequence of strings.
        without_suffix (): If ``True`` the suffix part of each isotope string is ignored.
        allow_invalid ():  If ``False``, and a string cannot be parsed into an isotope string, an exception is
            raised. If ``True`` then ``string.strip()`` is returned instead.

    Examples:
        >>> simple.asisotopes('104pd, pd105, 106-Pd')
        ('Pd-104', 'Pd-105, 106-Pd')

        >>> simple.asisotopes(['104pd', 'pd105', '106-Pd'])
        ('Pd-104', 'Pd-105, 106-Pd')
    """
    if type(strings) is str:
        strings = [s for s in strings.split(',')]

    return tuple(asisotope(string, without_suffix=without_suffix, allow_invalid=allow_invalid) for string in strings)

simple.utils.askeyarray

askeyarray(values, keys, dtype=None)

Returns a numpy array where the columns can be accessed by the column key.

Parameters:

  • values

    An array consisting of 2 dimensions where first dimension is the row and the second

  • keys

    The keys for each column in values. Must be the same length as the second dimension of values.

  • dtype

    The values type of the returned array. All columns will have the same dtype.

Notes If values has less then 2 dimensions then it is assumed to represent a single row of values.

It is not possible to save this type of array in hdf5 files if they have more than a few hundred columns.

Examples:

>>> a = simple.askeyarray([[1,2,3],[4,5,6]], ['Pd-104','Pd-105','Pd-106']); a
array([(1, 2, 3), (4, 5, 6)],
      dtype=[('Pd-104', '<i8'), ('Pd-105', '<i8'), ('Pd-106', '<i8')])
>>> a['Pd-104']
array([1, 4])
Source code in simple/utils.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
def askeyarray(values, keys, dtype=None):
    """
    Returns a numpy array where the columns can be accessed by the column key.

    Args:
        values (): An array consisting of 2 dimensions where first dimension is the row and the second
        dimension is the column.
        keys (): The keys for each column in ``values``. Must be the same length as the second dimension of ``values``.
        of ``array``.
        dtype (): The values type of the returned array. All columns will have the same dtype.

    **Notes**
    If ``values`` has less then 2 dimensions then it is assumed to represent a single row of values.

    It is not possible to save this type of array in hdf5 files if they have more than a few hundred columns.

    Examples:
        >>> a = simple.askeyarray([[1,2,3],[4,5,6]], ['Pd-104','Pd-105','Pd-106']); a
        array([(1, 2, 3), (4, 5, 6)],
              dtype=[('Pd-104', '<i8'), ('Pd-105', '<i8'), ('Pd-106', '<i8')])
        >>> a['Pd-104']
        array([1, 4])

    """
    if type(keys) is str:
        keys = [k.strip() for k in keys.split(',')]

    a = np.asarray(values, dtype=dtype)
    dtype = [(k, a.dtype) for k in keys]

    if a.ndim < 2:
        a = a.reshape((-1, a.size))
    elif a.ndim > 2:
        raise ValueError('``values`` cannot have more than 2 dimensions')

    if a.shape[1] != len(keys):
        raise ValueError(
            f'item (r:{a.shape[0]}, c:{a.shape[1]}) must have same number of columns as there are keys ({len(keys)})')

    return np.array([tuple(r) for r in a], dtype=dtype)

simple.utils.asratio

asratio(string, without_suffix=False, allow_invalid=False)

Returns a Ratio string representing the ratio of two isotopes.

The format of the returned string is the numerator followed by a / followed by the normiso. The numerator and normiso string be parsed by asisotope together with the given without_suffix and allow_invalid arguments passed to this function.

Parameters:

  • string (str) –

    A string contaning two strings seperated by a single /.

  • without_suffix (bool, default: False ) –

    If True the suffix part of the numerator and normiso string is ignored.

  • allow_invalid (bool, default: False ) –

    Whether the numerator and normiso has to be a valid isotope string.

If the returned string is an isotope string it will have the following attributes and methods.

Attributes:

  • numer (str) –

    The numerator string

  • denom (str) –

    The normiso string

Functions:

  • latex

    Returns a latex formatted version of the isotope.

  • without_suffix

    Returns a ratio string omitting the numerator and normiso suffix.

Source code in simple/utils.py
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
def asratio(string, without_suffix=False, allow_invalid=False):
    """
    Returns a [``Ratio``][simple.utils.Isotope] string representing the ratio of two isotopes.

    The format of the returned string is the numerator followed by
    a ``/`` followed by the normiso. The numerator and normiso string be parsed by ``asisotope`` together with
    the given ``without_suffix`` and ``allow_invalid`` arguments passed to this function.

    Args:
        string (str): A string contaning two strings seperated by a single ``/``.
        without_suffix (bool): If ``True`` the suffix part of the numerator and normiso string is ignored.
        allow_invalid (bool): Whether the numerator and normiso has to be a valid isotope string.

    If the returned string is an isotope string it will have the following attributes and methods.

    Attributes:
        numer (str): The numerator string
        denom (str): The normiso string

    Methods:
        latex(string): Returns a latex formatted version of the isotope.
        without_suffix(): Returns a ratio string omitting the numerator and normiso suffix.

    """
    if type(string) is Ratio:
        return string
    elif isinstance(string, str):
        string = string.strip()
    else:
        raise TypeError(f'``string`` must a str not {type(string)}')

    try:
        return Ratio(string, without_suffix=without_suffix)
    except ValueError:
        if allow_invalid:
            return string
        else:
            raise

simple.utils.asratios

asratios(strings, without_suffix=False, allow_invalid=False)

Returns a tuple of Ratio strings where each string represents the ratio of two isotopes.

Parameters:

  • strings

    Can either be a string with isotope ratios seperated by a , or a sequence of strings.

  • without_suffix

    If True the suffix part of each isotope string is ignored.

  • allow_invalid

    If False, and a string cannot be parsed into an isotope string, an exception is raised. If True then string.strip() is returned instead.

Source code in simple/utils.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
def asratios(strings, without_suffix=False, allow_invalid=False):
    """
    Returns a tuple of [``Ratio``][simple.utils.Isotope] strings where each string represents the ratio of two isotopes.

    Args:
        strings (): Can either be a string with isotope ratios seperated by a ``,`` or a sequence of strings.
        without_suffix (): If ``True`` the suffix part of each isotope string is ignored.
        allow_invalid ():  If ``False``, and a string cannot be parsed into an isotope string, an exception is
            raised. If ``True`` then ``string.strip()`` is returned instead.
    """
    if type(strings) is str:
        strings = [s.strip() for s in strings.split(',')]

    return tuple(asratio(string, without_suffix=without_suffix, allow_invalid=allow_invalid) for string in strings)

simple.utils.deprecation_warning

deprecation_warning(message)
Source code in simple/utils.py
266
267
268
269
270
271
272
273
def deprecation_warning(message):
    def inner(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            logger.warning(f'DeprecationWarning: {message}')
            return func(*args, **kwargs)
        return wrapper
    return inner

simple.utils.extract_kwargs

extract_kwargs(kwargs, *keys, prefix=None, pop=True, remove_prefix=True, **initial_kwargs)

Extracts the given keyword arguments from kwargs.

Parameters:

  • kwargs

    The dictionary from which the keyword arguments will be extracted.

  • *keys

    Keyword arguments to be extracted

  • prefix

    Any keyword with this prefix will be extracted. A "_" will be added to the end of the prefix if not already present.

  • pop

    Whether to remove the extracted keyword arguments from kwargs.

  • remove_prefix

    If True the prefix part of they keyword is removed from the returned dictionary.

  • **initial_kwargs

    Any additional keyword arguments. Note that these will be overwritten if the same

Returns:

  • dict

    A dictionary containing the extracted keyword arguments.

Source code in simple/utils.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def extract_kwargs(kwargs, *keys, prefix=None, pop=True, remove_prefix=True, **initial_kwargs):
    """
    Extracts the given keyword arguments from ``kwargs``.

    Args:
        kwargs (): The dictionary from which the keyword arguments will be extracted.
        *keys (): Keyword arguments to be extracted
        prefix (): Any keyword with this prefix will be extracted. A ``"_"`` will be added to the end of the
            prefix if not already present.
        pop (): Whether to remove the extracted keyword arguments from ``kwargs``.
        remove_prefix ():  If ``True`` the prefix part of they keyword is removed from the returned dictionary.
        **initial_kwargs (): Any additional keyword arguments. Note that these will be overwritten if the same
        keyword is extracted from ``kwargs``.

    Returns:
        dict: A dictionary containing the extracted keyword arguments.
    """
    extracted = initial_kwargs

    for k in keys:
        if k in kwargs:
            if pop:
                extracted[k] = kwargs.pop(k)
            else:
                extracted[k] = kwargs.get(k)

    if not isinstance(prefix, (list, tuple)):
        prefix = (prefix, )

    for prfx in prefix:
        if type(prfx) is not str:
            continue

        if prfx[-1] != '_': prfx += '_'

        for k in list(kwargs.keys()):
            if k[:len(prfx)] == prfx:
                if remove_prefix:
                    name = k[len(prfx):]
                else:
                    name = k
                if pop:
                    extracted[name] = kwargs.pop(k)
                else:
                    extracted[name] = kwargs.get(k)

    return extracted

simple.utils.get_isotopes_of_element

get_isotopes_of_element(isotopes, element, isotopes_without_suffix=False)

Returns a tuple of all isotopes in a sequence that contain the given element symbol.

Note The strings in isotopes will be passed through asisotopes before the evaluation and therefore do not have to be correcly formatted. Invalid isotope string are allowed but will be ignored by the evaluation.

Parameters:

  • isotopes

    An iterable of strings representing isotopes.

  • element (str) –

    The element symbol.

  • suffix (str) –

    If given the isotopes must also have this suffix.

  • isotopes_without_suffix (bool, default: False ) –

    If True suffixes will be removed from the isotopes in isotopes before the evaluation takes place.

Examples:

>>> simple.utils.get_isotopes_of_element(["Ru-101", "Pd-102", "Rh-103", "Pd-104"], "Pd")
>>> ("Pd-102", "Pd-104")
Source code in simple/utils.py
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
def get_isotopes_of_element(isotopes, element, isotopes_without_suffix=False):
    """
    Returns a tuple of all isotopes in a sequence that contain the given element symbol.

    **Note** The strings in ``isotopes`` will be passed through [asisotopes][simple.asisotopes] before
    the evaluation and therefore do not have to be correcly formatted. Invalid isotope string are allowed
    but will be ignored by the evaluation.

    Args:
        isotopes (): An iterable of strings representing isotopes.
        element (str): The element symbol.
        suffix (str): If given the isotopes must also have this suffix.
        isotopes_without_suffix (bool): If ``True`` suffixes will be removed from the isotopes in ``isotopes``
            before the evaluation takes place.

    Examples:
        >>> simple.utils.get_isotopes_of_element(["Ru-101", "Pd-102", "Rh-103", "Pd-104"], "Pd")
        >>> ("Pd-102", "Pd-104")
    """
    isotopes = asisolist(isotopes, without_suffix=isotopes_without_suffix, allow_invalid=True)
    element = aselement(element)
    return tuple(iso for iso in isotopes if (type(iso) is Isotope and iso.element == element))

simple.utils.get_last_attr

get_last_attr(item, attrname, default=OptionalArg)
Source code in simple/utils.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def get_last_attr(item, attrname, default=OptionalArg):
    # Return the final attribute in the chain ``attrname`` starting from ``item``
    attrnames = parse_attrname(attrname).split('.')
    attr = item
    for i, name in enumerate(attrnames):
        try:
            if isinstance(attr, dict):
                attr = attr[name]
            else:
                attr = getattr(attr, name)
        except (AttributeError, KeyError):
            if default is not OptionalArg:
                # Log message?
                return default
            else:
                raise AttributeError(f'Item {item} has no attribute {".".join(attrnames[:i+1])}')
    return attr

simple.utils.load_defaults

load_defaults(filename)

Loads default arguments for functions from a YAML formatted file.

To use a set of default values, unpack the arguments in the function call (See example).

You can still arguments and keyword arguments as normal as long as they are not included in the default dictionary.

Returns:

  • A named dictionary containing mapping the prefixes given in the yaml file to another dictionary mapping the arguments

  • to the specified values.

Examples:

The file default.yaml is expected to look like this:

somefunction:
    arg: value
    listarg:
        - first thing in list
        - second thing in list

anotherfunction:
    arg: value

It can be used like this

>>> defaults = simple.load_defaults('defaults.yaml')
>>> somefunction(**defaults['somefunction']) # Unpack arguments into function call
Source code in simple/utils.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def load_defaults(filename: str):
    """
    Loads default arguments for functions from a YAML formatted file.

    To use a set of default values, unpack the arguments in the function call (See example).

    You can still arguments and keyword arguments as normal as long as they are not included in the default dictionary.

    Returns:
        A named dictionary containing mapping the prefixes given in the yaml file to another dictionary mapping the arguments
        to the specified values.

    Examples:
        The file ``default.yaml`` is expected to look like this:
        ```
        somefunction:
            arg: value
            listarg:
                - first thing in list
                - second thing in list

        anotherfunction:
            arg: value
        ```

        It can be used like this
        >>> defaults = simple.load_defaults('defaults.yaml')
        >>> somefunction(**defaults['somefunction']) # Unpack arguments into function call


    """
    return dict(yaml.safe_load(open(filename, 'r').read()))

simple.utils.parse_attrname

parse_attrname(attrname)
Source code in simple/utils.py
47
48
49
50
51
def parse_attrname(attrname):
    if attrname is None:
        return None
    else:
        return '.'.join([aa for a in attrname.split('.') if (aa := a.strip()) != ''])

simple.utils.select_isolist

select_isolist(isolist, data, *, without_suffix=False)

Creates a subselection of data containing only the isotopes in isolist.

If multiple input isotopes are given for an output isotope in isolist the values of the input isotopes will be added together. Any isotopes missing from data will be given a value of 0.

When using this function to account for radioactive decay you want the unit of data to be in moles.

Parameters:

  • isolist

    Either a list of isotopes to be selected or a dictionary consisting of the final isotope mapped to a list of isotopes to be added together for this isotope.

  • data

    A key array from which the subselection will be made.

  • without_suffix

    If True the suffix will be removed from all isotope strings in isolist.

Returns:

  • A new key array containing only the isotopes in isolist.

Source code in simple/utils.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
def select_isolist(isolist, data, *, without_suffix=False):
    """
    Creates a subselection of ``data`` containing only the isotopes in ``isolist``.

    If multiple input isotopes are given for an output isotope in ``isolist`` the values of the input isotopes will
    be added together. Any isotopes missing from ``data`` will be given a value of 0.

    When using this function to account for radioactive decay you want the unit of ``data`` to be in moles.

    Args:
        isolist (): Either a list of isotopes to be selected or a dictionary consisting of the
             final isotope mapped to a list of isotopes to be added together for this isotope.
        data (): A key array from which the subselection will be made.
        without_suffix (): If ``True`` the suffix will be removed from all isotope strings in ``isolist``.


    Returns:
        A new key array containing only the isotopes in ``isolist``.

    """
    isolist = asisolist(isolist, without_suffix=without_suffix)
    data = np.asarray(data)

    new_data = []
    missing_isotopes = []

    if data.dtype.names is None:
        raise ValueError('``data`` must be a key arrray')

    for mainiso, inciso in isolist.items():
        value = np.zeros(data.shape)

        for iso in inciso:
            if iso in data.dtype.names:
                value += data[iso]
            else:
                missing_isotopes.append(iso)

        new_data.append(value)

    result = askeyarray(np.array(new_data).transpose(), isolist.keys())

    if len(missing_isotopes) != 0:
        logger.warning(f'Missing isotopes set to 0: {", ".join(missing_isotopes)}')

    return result

simple.utils.set_default_kwargs

set_default_kwargs(inherits=False, **default_kwargs)

Decorator sets the default keyword arguments for the function. It wraps the function so that the default kwargs are always passed to the function.

The default_kwargs can be accessed from <func>.default_kwargs. To update the dictionary use the function update_default_kwargs attached to the return function.

Source code in simple/utils.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def set_default_kwargs(inherits = False, **default_kwargs):
    """
    Decorator sets the default keyword arguments for the function. It wraps the function so that the
    default kwargs are always passed to the function.

    The default_kwargs can be accessed from ``<func>.default_kwargs``. To update the dictionary use the function
    ``update_default_kwargs`` attached to the return function.
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            new_kwargs = get_default_kwargs()
            new_kwargs.update(kwargs)
            return func(*args, **new_kwargs)

        def add_shortcut_(name, inherits = True, **shortcut_kwargs):
            return add_shortcut(name, inherits = inherits, **shortcut_kwargs)(wrapper)

        def get_default_kwargs():
            if inherits is True:
                new_kwargs = getattr(func, 'default_kwargs', {})
            elif inherits is not False and inherits is not None:
                new_kwargs = getattr(inherits, 'default_kwargs', {})
            else:
                new_kwargs = {}
            print(type(new_kwargs))
            new_kwargs.update(default_kwargs)
            return new_kwargs

        def update_default_kwargs(clear_=False, remove_=None, **kwargs):
            if clear_ is True:
                default_kwargs.clear()
            if type(remove_) is str:
                remove_ = [remove_]
            if isinstance(remove_, (list, tuple)):
                for r_ in remove_:
                    default_kwargs.pop(r_, None)

            default_kwargs.update(kwargs)

        wrapper._default_kwargs = default_kwargs
        wrapper.wrapped = func

        wrapper.update_default_kwargs = update_default_kwargs
        wrapper.default_kwargs = property(get_default_kwargs)
        wrapper.add_shortcut = add_shortcut_

        return DefaultKwargsWrapper(func, default_kwargs, inherits)
    return decorator

simple.utils.set_logging_level

set_logging_level(level)

Set the level of messages to be displayed.

Options are: DEBUG, INFO, WARNING, ERROR.

Source code in simple/utils.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def set_logging_level(level):
    """
    Set the level of messages to be displayed.

    Options are: DEBUG, INFO, WARNING, ERROR.
    """
    if level.upper() == 'DEBUG':
        level = logging.DEBUG
    elif level.upper() == 'INFO':
        level = logging.INFO
    elif level.upper() == 'WARNING':
        level = logging.WARNING
    elif level.upper() == 'ERROR':
        level = logging.ERROR

    SimpleLogger.setLevel(level)

simple.utils.update_docs

update_docs(**kwargs)
Source code in simple/utils.py
118
119
120
121
122
def update_docs(**kwargs):
    def inner(func):
        func.__doc__ = 'I changed it!'
        return func
    return inner