Skip to content

The simple.models Namespace

The models submodule contains everything pertaining the primary object structure which keeps track of different models.


simple.models.AllModelClasses module-attribute

AllModelClasses = {}

A dictionary containing all the avaliable model classes.

When a new model class is created subclassing ModelBase it is automatically added to this dictionary.

simple.models.HDF5Dict

HDF5Dict(*args, **kwargs)

Bases: NamedDict

A subclass of NamedDict where all values are passed to asarray before being added to the dictionary.

All contents on this dictionary should be compatiable with HDF5 files.

Examples:

>>> nd = simple.utils.NamedDict({'a': 1, 'b': 2, 'c': 3})
>>> nd.a
array(1)

Initialise the dictionary and track attribute types.

Source code in simple/models.py
28
29
30
31
def __init__(self, *args, **kwargs):
    """Initialise the dictionary and track attribute types."""
    super().__setattr__('_attr_type', {}, item=False)
    super().__init__(*args, **kwargs)

simple.models.IsoRef

IsoRef(name, reference_models_=None, **hdf5_attrs)

Bases: ModelBase

Model specifically for storing reference isotope values.

Attributes:

  • type (str) –

    The type of data stored in the model. Required at initialisation

  • citation (str) –

    A citation for the data. Required at initialisation

  • data

    A key array containing the data. Is created upon model initiation from the data_values and data_keys attributes.

  • data_values

    A 2dim array containing the data. Required at initialisation

  • data_keys

    Keys for the second dimension of data_values. Required at initialisation

  • data_unit

    Unit for the data. Required at initialisation

Create a model instance and populate hdf5_attrs.

Source code in simple/models.py
594
595
596
597
598
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
def __init__(self, name, reference_models_ = None, **hdf5_attrs):
    """Create a model instance and populate ``hdf5_attrs``."""
    super().__setattr__('name', name)
    super().__setattr__('hdf5_attrs', HDF5Dict())
    super().__setattr__('normal_attrs', NamedDict())
    super().__setattr__('_hash', hash(object()))

    for attr in self.REQUIRED_ATTRS:
        if attr not in hdf5_attrs:
            raise ValueError(f"{{Required attribute '{attr}' does not exist in initialisation arguments}}")

    for k, v in hdf5_attrs.items():
        self.setattr(k, v, hdf5_compatible=True)

    # This is how we know which class to create. Need to be set after attrs incase you remapp the
    # class name
    self.setattr('clsname', self.__class__.__name__, hdf5_compatible=True, overwrite=True)

    # Automatically creates <name> key array if <name>_values and <name>_keys exists
    if self.VALUES_KEYS_TO_ARRAY:
        for key in self.hdf5_attrs:
            if key[-7:] == '_values':
                aattr = key[:-7]
                vattr = key
                kattr = key[:-7] + '_keys'
                if kattr in self.hdf5_attrs and aattr not in self.hdf5_attrs:
                    v = self.hdf5_attrs[vattr]
                    k = self.hdf5_attrs[kattr]
                    self.setattr(aattr, utils.askeyarray(v, k), hdf5_compatible=False)

    for key, value in self.hdf5_attrs.items():
        if key[:6] == 'refid_':
            id_name = key[6:]
            if reference_models_ is None:
                raise ValueError(f"No reference models were supplied")
            for ref in reference_models_:
                if ref.name == value:
                    self.setattr(f'ref_{id_name}', ref, hdf5_compatible=False)
                    break
            else:
                raise ValueError(f"No model called '{value}' was found in the supplied reference models")

ISREF class-attribute instance-attribute

ISREF = True

REPR_ATTRS class-attribute instance-attribute

REPR_ATTRS = ['name', 'type']

REQUIRED_ATTRS class-attribute instance-attribute

REQUIRED_ATTRS = ['type', 'citation', 'data_values', 'data_keys', 'data_unit']

simple.models.ModelBase

ModelBase(name, reference_models_=None, **hdf5_attrs)

This class can be subclassed to create new model classes.

Once subclassed the new model will automatically be available through ModelCollection.new_model.

There are a number of class attributes that can be set to determine behaviour of the new class:

  • REQUIRED_ATTRS - A list of attributes that must be supplied when creating the class. An exception will be raised if any of these attributes are missing.
  • REPR_ATTRS - A list of the attributes that values will be shown in the repr.
  • ABUNDANCE_KEYARRAY - The name of a key array containing the abundances that should be normalised. Alternatively you can subclass the internal_normalisation and standard_normalisation methods for more customisation.
  • VALUES_KEYS_TO_ARRAY - If True a key array named <name> is automatically created upon model initialisation if attributes called. <name>_values and <name>_keys exits.

Create a model instance and populate hdf5_attrs.

Source code in simple/models.py
594
595
596
597
598
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
def __init__(self, name, reference_models_ = None, **hdf5_attrs):
    """Create a model instance and populate ``hdf5_attrs``."""
    super().__setattr__('name', name)
    super().__setattr__('hdf5_attrs', HDF5Dict())
    super().__setattr__('normal_attrs', NamedDict())
    super().__setattr__('_hash', hash(object()))

    for attr in self.REQUIRED_ATTRS:
        if attr not in hdf5_attrs:
            raise ValueError(f"{{Required attribute '{attr}' does not exist in initialisation arguments}}")

    for k, v in hdf5_attrs.items():
        self.setattr(k, v, hdf5_compatible=True)

    # This is how we know which class to create. Need to be set after attrs incase you remapp the
    # class name
    self.setattr('clsname', self.__class__.__name__, hdf5_compatible=True, overwrite=True)

    # Automatically creates <name> key array if <name>_values and <name>_keys exists
    if self.VALUES_KEYS_TO_ARRAY:
        for key in self.hdf5_attrs:
            if key[-7:] == '_values':
                aattr = key[:-7]
                vattr = key
                kattr = key[:-7] + '_keys'
                if kattr in self.hdf5_attrs and aattr not in self.hdf5_attrs:
                    v = self.hdf5_attrs[vattr]
                    k = self.hdf5_attrs[kattr]
                    self.setattr(aattr, utils.askeyarray(v, k), hdf5_compatible=False)

    for key, value in self.hdf5_attrs.items():
        if key[:6] == 'refid_':
            id_name = key[6:]
            if reference_models_ is None:
                raise ValueError(f"No reference models were supplied")
            for ref in reference_models_:
                if ref.name == value:
                    self.setattr(f'ref_{id_name}', ref, hdf5_compatible=False)
                    break
            else:
                raise ValueError(f"No model called '{value}' was found in the supplied reference models")

ABUNDANCE_KEYARRAY class-attribute instance-attribute

ABUNDANCE_KEYARRAY = None

REPR_ATTRS class-attribute instance-attribute

REPR_ATTRS = ['name']

REQUIRED_ATTRS class-attribute instance-attribute

REQUIRED_ATTRS = []

VALUES_KEYS_TO_ARRAY class-attribute instance-attribute

VALUES_KEYS_TO_ARRAY = True

convert_array

convert_array(array, unit, desired_unit, *, attrname='')

Return a copy of the array converted to the desired unit.

Supported units are the mole and mass units. Converting between the mass and mole* units is done by dividing/multiplying the values by the mass number.

Always return a copy of array even if not conversion takes place.

Parameters:

  • array

    A key array.

  • unit

    The current unit of the data in array. If None the unit is assumed to be that of the desired_unit_args.

  • desired_unit

    The unit the array should be converted to. If None no conversion is made and the original array`is returned.

  • attrname

    The name of the attribute storing the array. Used for logging purposes.

Raises:

  • ValueError

    If the array cannot be converted from unit to desired_unit_args.

Source code in simple/models.py
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
def convert_array(self, array, unit, desired_unit, *, attrname = ''):
    """
    Return a copy of the array converted to the desired unit.

    Supported units are the [mole and mass units][simple.utils.UNITS]. Converting between the *mass* and *mole** units is done by
    dividing/multiplying the values by the mass number.

    Always return a copy of ``array`` even if not conversion takes place.

    Args:
        array (): A key array.
        unit (): The current unit of the data in ``array``. If ``None`` the unit is assumed to be that of the
            ``desired_unit_args``.
        desired_unit (): The unit the array should be converted to. If ``None`` no conversion is made
            and the original array`is returned.
        attrname (): The name of the attribute storing the array. Used for logging purposes.

    Raises:
        ValueError: If the array cannot be converted from ``unit`` to ``desired_unit_args``.
    """
    array = array.copy()

    if desired_unit is None:
        logger.debug(f"{self.name}{('.' + attrname) if attrname else ''}: No desired unit specified. Assuming the desired unit is the current unit")
        return array, unit
    elif unit is None:
        logger.warning(f"{self.name}{('.' + attrname) if attrname else ''}: Array does not have an assigned unit. Assuming the unit is the desired unit")
        return array, desired_unit

    if unit in utils.UNITS['mole']:
        if desired_unit.lower() in utils.UNITS['mole']:
            logger.debug(f"{self.name}{('.' + attrname) if attrname else ''}:Both array unit and desired unit are ``mole`` units.")
            return array, unit
        elif desired_unit.lower() in utils.UNITS['mass']:
            if array.dtype.names is None:
                raise ValueError(f"{self.name}{('.' + attrname) if attrname else ''}: Can only convert isotope key arrays")
            logger.info(f"{self.name}{('.' + attrname) if attrname else ''}: Converting array from ``mole`` to ``mass`` unit by multiplying the data by the mass number")
            for key in array.dtype.names:
                try:
                    m = float(utils.asisotope(key).mass)
                except ValueError:
                    pass # Leaves non-isotope values unchanged
                else:
                    array[key] *= m
            return array, desired_unit

    elif unit in utils.UNITS['mass']:
        if desired_unit.lower() in utils.UNITS['mass']:
            logger.debug(f"{self.name}{('.' + attrname) if attrname else ''}: Both array unit and desired unit are ``mass`` units.")
            return array, unit
        elif desired_unit.lower() in utils.UNITS['mole']:
            if array.dtype.names is None:
                raise ValueError(f"{self.name}{('.' + attrname) if attrname else ''}: Can only convert isotope key arrays")

            logger.info(f"{self.name}{('.' + attrname) if attrname else ''}: Converting array from ``mass`` to ``mole`` unit by dividing the data by the mass number")
            for key in array.dtype.names:
                try:
                    m = float(utils.asisotope(key).mass)
                except ValueError:
                    pass # Leaves non-isotope values unchanged
                else:
                    array[key] /= m
            return array, desired_unit

    raise ValueError(f"{self.name}{('.' + attrname) if attrname else ''}: Unable to convert from '{unit}' to '{desired_unit}'")

get_array

get_array(name=None, desired_unit=None)

Returns a copy of the named array with the desired unit.

Parameters:

  • name

    Name of the array to return. If None the default abundance array is returned.

  • desired_unit

    The desired unit of the returned array.

Returns:

  • A copy of the array with the desired unit.

Source code in simple/models.py
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
def get_array(self, name=None, desired_unit=None):
    """
    Returns a copy of the named array with the desired unit.

    Args:
        name (): Name of the array to return. If ``None`` the default abundance array is returned.
        desired_unit (): The desired unit of the returned array.

    Returns:
        A copy of the array with the desired unit.
    """
    if name is None:
        if self.ABUNDANCE_KEYARRAY is None:
            raise ValueError(f"{self.name}: No default array associated with this model")
        else:
            name = self.ABUNDANCE_KEYARRAY

    try:
        a = utils.get_last_attr(self, name)
    except KeyError:
        raise AttributeError(f"{self.name}: This model has no attribute called '{name}'")
    else:
        if not isinstance(a, np.ndarray):
            raise TypeError(f"{self.name}: Model attribute '{name}' is not an array")

    unit = utils.get_last_attr(self, f"{name}_unit", None)
    if desired_unit is None:
        return a.copy(), unit
    elif unit is None:
        logger.warning(f"{self.name}: Keyarray '{name}' has no unit attribute. Assuming unit is {desired_unit}")
        return a.copy(), desired_unit
    else:
        return self.convert_array(a, unit, desired_unit, attrname=name)

    if desired_unit is None:
        return a.copy(), desired_unit
    else:
        unit = utils.get_last_attr(self, f"{name}_unit", None)
        if unit is None:
            logger.warning(f"{self.name}: Keyarray '{name}' has no unit attribute. Assuming unit is {desired_unit}")
            return a.copy(), desired_unit
        else:
            return self.convert_array(a, unit, desired_unit)

get_array_labels

get_array_labels(name=None, latex=True)

Return default labels for a key array.

Source code in simple/models.py
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
def get_array_labels(self, name=None, latex=True):
    """Return default labels for a key array."""
    if name is None:
        if self.ABUNDANCE_KEYARRAY is None:
            raise ValueError(f"{self.name}: No default array associated with this model")
        else:
            name = self.ABUNDANCE_KEYARRAY

    data_label = None
    key_labels = None
    if latex:
        data_label = utils.get_last_attr(self, f"{name}_label_latex", None)
        key_labels = utils.get_last_attr(self, f"{name}_keylabels_latex", None)

    if data_label is None:
        data_label = utils.get_last_attr(self, f"{name}_label", None)

    if key_labels is None:
        key_labels = utils.get_last_attr(self, f"{name}_keylabels", None)

    if data_label is None:
        data_label = utils.parse_attrname(name)

    if key_labels is None:
        a = utils.get_last_attr(self, name, None)
        if a is not None and isinstance(a, np.ndarray) and a.dtype.names is not None:
            try:
                key_labels = utils.asisotopes(a.dtype.names)
            except Exception as error:
                key_labels = None
            else:
                if latex:
                    key_labels = {k: k.latex() for k in key_labels}
                else:
                    key_labels = {k: k for k in key_labels}
        else:
            key_labels = None

    return data_label, key_labels

get_mask

get_mask(mask, shape=None, **mask_attrs)

Returns a selection mask for an array with shape.

This function is used by plotting functions to plot only a sub selction of the data. The mask string can an integer representing an index, a slice or a condition that generates a mask. Use & or | to combine multiple indexes and/or conditions.

Supplied attributes can be accesed by putting a dot infront of the name, e.g. .data > 1. The available operators for mask conditions are ==, !=, >, >=, <, <=.

The result of the mask evaluation must be broadcastable with shape. If it is not an all False mask is returned.

Note - It is not possible to mix & and | seperators. Doing so will raise an exception. - Any text not precceded by a dot will be evaluated as text. Text on its own will always be evaluated as False. - An empty string will be evaluated as True

Parameters:

  • mask

    String or object that will be evaluated to create a mask.

  • shape

    Shape of the returned mask. If omitted the shape of the default abundance array is used.

  • **mask_attrs

    Attributes to be used during the evaluation.

Examples:

>>> a = np.array([0,1,2,3,4])
>>> model.get_mask('3', a.shape)
array([False, False, False,  True,  False])
>>> model.get_mask('1:3', a.shape)
array([False, True, True,  False,  False])
>>> model.get_mask('.data >= 1 & .data < 3', a.shape, data=a)
array([False, True, True,  False,  False])
>>> model.get_mask('.data >= 1 | .data > 3', a.shape, data=a)
rray([True, True, False,  False,  True])

Returns:

  • A boolean numpy array with shape.

Source code in simple/models.py
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
def get_mask(self, mask, shape = None, **mask_attrs):
    """
    Returns a selection mask for an array with ``shape``.

    This function is used by plotting functions to plot only a sub selction of the data. The mask string
    can an integer representing an index, a slice or a condition that generates a mask. Use ``&`` or ``|``
    to combine multiple indexes and/or conditions.

    Supplied attributes can be accesed by putting a dot infront of the name, e.g. ``.data > 1``. The available
    operators for mask conditions are ``==``, ``!=``, ``>``, ``>=``, ``<``, ``<=``.

    The result of the mask evaluation must be broadcastable with ``shape``. If it is not an all ``False`` mask is
    returned.

    **Note**
    - It is not possible to mix ``&`` and ``|`` seperators. Doing so will raise an exception.
    - Any text not precceded by a dot will be evaluated as text. Text on its own will always be evaluated
    as ``False``.
    - An empty string will be evaluated as ``True``


    Args:
        mask (): String or object that will be evaluated to create a mask.
        shape (): Shape of the returned mask. If omitted the shape of the default abundance array is used.
        **mask_attrs (): Attributes to be used during the evaluation.


    Examples:
        >>> a = np.array([0,1,2,3,4])
        >>> model.get_mask('3', a.shape)
        array([False, False, False,  True,  False])

        >>> model.get_mask('1:3', a.shape)
        array([False, True, True,  False,  False])

        >>> model.get_mask('.data >= 1 & .data < 3', a.shape, data=a)
        array([False, True, True,  False,  False])

        >>> model.get_mask('.data >= 1 | .data > 3', a.shape, data=a)
        rray([True, True, False,  False,  True])

    Returns:
        A boolean numpy array with ``shape``.

    """
    if shape is None:
        if self.ABUNDANCE_KEYARRAY is None:
            raise ValueError(f"{self.name}: Shape is required as there is no default key array associated with this model")
        else:
            shape = getattr(self, self.ABUNDANCE_KEYARRAY).shape

    return utils.mask_eval.eval(self, mask, shape, **mask_attrs)

internal_normalisation

internal_normalisation(normrat, *, isotopes=None, enrichment_factor=1, relative_enrichment=True, convert_unit=True, attrname='intnorm', method='largest_offset', **method_kwargs)

Internally normalise the appropriate data of the model. See internal_normalisation for a description of the procedure and a description of the arguments.

The result of the normalisation will be saved under the normname attribute.

Raises:

  • NotImplementedError

    Raised if the data to be normalised has not been specified for this model class.

Source code in simple/models.py
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
def internal_normalisation(self, normrat, *, isotopes = None,
                           enrichment_factor=1, relative_enrichment=True,
                           convert_unit=True, attrname='intnorm',
                           method='largest_offset', **method_kwargs):
    """
    Internally normalise the appropriate data of the model. See
    [internal_normalisation][simple.norm.internal_normalisation] for a description of the procedure and a
    description of the arguments.

    The result of the normalisation will be saved under the ``normname`` attribute.

    Raises:
        NotImplementedError: Raised if the data to be normalised has not been specified for this model class.
    """
    if self.ABUNDANCE_KEYARRAY is None:
        raise NotImplementedError(f'{self.name}: The data to be normalised in has not been specified for this model')

    # The abundances to be normalised
    abu, abu_unit = self.get_array(self.ABUNDANCE_KEYARRAY, 'mol' if convert_unit else None)

    # Isotope masses
    stdmass, stdmass_unit = self.ref_isomass.get_array('data')

    # The reference abundances. Typically, the initial values of the model
    stdabu, stdabu_unit = self.ref_isoabu.get_array('data', 'mol' if convert_unit else None)

    result = norm.internal_normalisation(abu, isotopes, normrat, stdmass, stdabu,
                                         enrichment_factor=enrichment_factor, relative_enrichment=relative_enrichment,
                                         method=method,
                                         msg_prefix = f'{self.name}.{utils.parse_attrname(self.ABUNDANCE_KEYARRAY)}',
                                         **method_kwargs)

    self.setattr(attrname, result, hdf5_compatible=False, overwrite=True)
    return result

select_isolist

select_isolist(isolist, convert_unit=True)

Used to create a subselection of the data used for normalisation in the current model.

Notes

The original array will be overwritten with the new subselection.

If <name>_values and <name>_keys exist they will also be overwritten with the result of the new selection.

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.

  • convert_unit

    If True and data is stored in a mass unit all values will be divided by the mass number of the isotope before summing values together. The final value is then multiplied by the mass number of the output isotope.

Raises:

  • NotImplementedError

    Raised if the data to be normalised has not been specified for this model class.

Source code in simple/models.py
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
def select_isolist(self, isolist, convert_unit=True):
    """
    Used to create a subselection of the data used for normalisation in the current model.

    **Notes**

    The original array will be overwritten with the new subselection.

    If ``<name>_values`` and ``<name>_keys`` exist they will also be overwritten with the
    result of the new selection.

    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.
        convert_unit: If ``True``  and data is stored in a mass unit all values will be divided by the mass number of
            the isotope before summing values together. The final value is then multiplied by the mass number of the
            output isotope.

    Raises:
        NotImplementedError: Raised if the data to be normalised has not been specified for this model class.
    """
    # Updates the relevant arrays inplace
    if self.ABUNDANCE_KEYARRAY is None:
        raise NotImplementedError(f'{self.name}: The data to be normalised in has not been specified for this model')

    abu, abu_unit = self.get_array(self.ABUNDANCE_KEYARRAY, 'mol' if convert_unit else None)

    abu = utils.select_isolist(isolist, abu)

    if convert_unit:
        original_unit = getattr(self, f"{self.ABUNDANCE_KEYARRAY}_unit", "mol")
        abu, abu_unit = self.convert_array(abu, 'mol', original_unit)

    self.setattr(self.ABUNDANCE_KEYARRAY, abu, hdf5_compatible=False, overwrite=True)

    vname = f"{self.ABUNDANCE_KEYARRAY}_values"
    if vname in self.hdf5_attrs:
        self.setattr(vname, np.asarray(abu.tolist()), hdf5_compatible=True, overwrite=True)
    elif vname in self.normal_attrs:
        self.setattr(vname, np.asarray(abu.tolist()), hdf5_compatible=False, overwrite=True)

    kname = f"{self.ABUNDANCE_KEYARRAY}_keys"
    if kname in self.hdf5_attrs:
        self.setattr(kname, utils.asisotopes(abu.dtype.names), hdf5_compatible=True, overwrite=True)
    elif kname in self.normal_attrs:
        self.setattr(kname, utils.asisotopes(abu.dtype.names), hdf5_compatible=False, overwrite=True)

setattr

setattr(name, value, hdf5_compatible=False, overwrite=False)

Set the value of a model attribute.

Parameters:

  • name

    Name of the attribute

  • value

    The value of the attribute

  • hdf5_compatible

    Should be True if the attribute should be included when the model is saved as a hdf5 file. value will be automatically converted to a hdf5 compatible value. An exception may be raised if this is not possible.

  • overwrite

    Overwrite any existing attribute called name. An exception is raised if name exists and overwrite is False.

Source code in simple/models.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
def setattr(self, name: str, value, hdf5_compatible: bool=False, overwrite:bool=False):
    """
    Set the value of a model attribute.

    Args:
        name (): Name of the attribute
        value (): The value of the attribute
        hdf5_compatible (): Should be ``True`` if the attribute should be included when the model is
            saved as a hdf5 file. ``value`` will be automatically converted to a hdf5 compatible value.
            An exception may be raised if this is not possible.
        overwrite (): Overwrite any existing attribute called ``name``. An exception is raised if ``name``
            exists and ``overwrite`` is ``False``.
    """
    if name in self.__dict__:
        raise AttributeError(f"'{self.__class__.__name__}' object has an attribute '{name}'")

    if (name in self.hdf5_attrs or name in self.normal_attrs) and overwrite is False:
        raise AttributeError(f"'{name}' already exists")
    elif name in self.hdf5_attrs:
        self.hdf5_attrs.pop(name)
    elif name in self.normal_attrs:
        if name in self.REQUIRED_ATTRS and hdf5_compatible is False:
            raise ValueError(f"{name} is a required attribute and therefore must be hdf5 compatible")
        else:
            self.normal_attrs.pop(name)

    if hdf5_compatible is True:
        self.hdf5_attrs.__setitem__(name, value)
    else:
        self.normal_attrs.__setitem__(name, value)

standard_normalisation

standard_normalisation(normiso, isotopes=None, *, enrichment_factor=1, relative_enrichment=True, convert_unit=True, attrname='stdnorm', dilution_factor=None)

Normalise the appropriate data of the model. See standard_normalisation for a description of the procedure and a description of the arguments.

The result of the normalisation will be saved under the normname attribute.

Raises:

  • NotImplementedError

    Raised if the data to be normalised has not been specified for this model class.

Source code in simple/models.py
 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
def standard_normalisation(self, normiso, isotopes = None, *, enrichment_factor = 1, relative_enrichment=True,
                         convert_unit=True, attrname='stdnorm', dilution_factor = None):
    """
    Normalise the appropriate data of the model. See
    [standard_normalisation][simple.norm.standard_normalisation] for a description of the procedure and a
    description of the arguments.

    The result of the normalisation will be saved under the ``normname`` attribute.

    Raises:
        NotImplementedError: Raised if the data to be normalised has not been specified for this model class.
    """
    if self.ABUNDANCE_KEYARRAY is None:
        raise NotImplementedError(f'{self.name}: The data to be normalised has not been specified for this model')

    abu, abu_unit = self.get_array(self.ABUNDANCE_KEYARRAY, 'mol' if convert_unit else None)

    stdabu, stdabu_unit = self.ref_isoabu.get_array('data', 'mol' if convert_unit else None)

    result = norm.standard_normalisation(abu, isotopes, normiso, stdabu,
                                         enrichment_factor=enrichment_factor, relative_enrichment=relative_enrichment,
                                         dilution_factor=dilution_factor,
                                         msg_prefix = f'{self.name}.{utils.parse_attrname(self.ABUNDANCE_KEYARRAY)}', )

    self.setattr(attrname, result, hdf5_compatible=False, overwrite=True)
    return result

simple.models.ModelCollection

ModelCollection()

The main interface for working with a collection of models.

Create an empty model collection.

Source code in simple/models.py
201
202
203
204
def __init__(self):
    """Create an empty model collection."""
    self.refs = []
    self.models = []

models instance-attribute

models = []

refs instance-attribute

refs = []

add_model

add_model(model)

Add model to the collection if it is not already present.

Source code in simple/models.py
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def add_model(self, model):
    """Add ``model`` to the collection if it is not already present."""
    if not isinstance(model, ModelBase):
        raise TypeError(f"``model`` must be a Model object, not {type(model)}")

    if model in self.models:
        return model

    if True in [m.name == model.name for m in self.models]:
        raise ValueError(f"A model with the name '{model.name}' already exists in this collection")
    else:
        self.models.append(model)

        for k, v in model.normal_attrs.items():
            if k[:4] == 'ref_' and isinstance(v, ModelBase):
                self.add_ref(v)
        return model

add_ref

add_ref(ref)

Add reference ref to the collection if it is not already present.

Source code in simple/models.py
451
452
453
454
455
456
457
458
459
460
461
462
463
def add_ref(self, ref):
    """Add reference ``ref`` to the collection if it is not already present."""
    if not isinstance(ref, ModelBase):
        raise TypeError(f"``ref`` must be a Model object, not {type(ref)}")

    if ref in self.refs:
        return ref

    if True in [m.name == ref.name for m in self.refs]:
        raise ValueError(f"A reference model with the name '{ref.name}' already exists in this collection")
    else:
        self.refs.append(ref)
        return ref

get_model

get_model(name, attr=None)

Returns the model with the given name.

If attr is given then the value of that attribute from the model is returned instead.

Source code in simple/models.py
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
def get_model(self, name, attr = None):
    """
    Returns the model with the given name.

    If ``attr`` is given then the value of that attribute from the model is returned instead.
    """
    for model in self.models:
        if model.name == name:
            break
    else:
        raise ValueError(f"No model called '{name}' exists")

    if attr is None:
        return model
    else:
        return model[attr]

get_ref

get_ref(name, attr=None)

Returns the reference model with the given name.

If attr is given then the value of that attribute from the model is returned instead.

Source code in simple/models.py
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
def get_ref(self, name, attr=None):
    """
    Returns the reference model with the given name.

    If ``attr`` is given then the value of that attribute from the model is returned instead.
    """
    for ref in self.refs:
        if ref.name == name:
            break
    else:
        raise ValueError(f"No reference called '{name}' exists")

    if attr is None:
        return ref
    else:
        return ref[attr]

internal_normalisation

internal_normalisation(normrat, *, isotopes=None, enrichment_factor=1, relative_enrichment=True, convert_unit=True, attrname='intnorm', method='largest_offset', **method_kwargs)

Internally normalise the appropriate data of the model. See internal_normalisation for a description of the procedure and a description of the arguments.

The result of the normalisation will be saved to each model under the normname attribute.

Raises:

  • NotImplementedError

    Raised if the data to be normalised has not been specified for this model class.

Source code in simple/models.py
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
def internal_normalisation(self, normrat, *, isotopes = None,
                           enrichment_factor=1, relative_enrichment=True,
                           convert_unit=True, attrname = 'intnorm',
                           method='largest_offset', **method_kwargs):
    """
    Internally normalise the appropriate data of the model. See
    [internal_normalisation][simple.norm.internal_normalisation] for a description of the procedure and a
    description of the arguments.

    The result of the normalisation will be saved to each model under the ``normname`` attribute.

    Raises:
        NotImplementedError: Raised if the data to be normalised has not been specified for this model class.
    """
    for model in self.models:
        model.internal_normalisation(normrat, isotopes=isotopes,
                                     enrichment_factor=enrichment_factor, relative_enrichment=relative_enrichment,
                                     convert_unit=convert_unit, attrname=attrname,
                                     method=method, **method_kwargs)

load_file

load_file(filename, isolist=None, convert_unit=True, where=None, **where_kwargs)

Add models from file to the current collection.

Note existing models with the same name one of the loaded models will be overwritten.

Parameters:

  • filename

    Name of the file to load.

  • isolist

    Isolist applied to loaded models. If None no subselection is made.

  • convert_unit

    Whether to convert units to the mole unit, as recommended, when creating the isolist.

  • where

    String evaluation used to select which models to load.

  • **where_kwargs

    Additional keyword arguments used together with where.

Source code in simple/models.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def load_file(self, filename, isolist=None, convert_unit=True, where=None, **where_kwargs):
    """
    Add models from file to the current collection.

    **Note** existing models with the same name one of the loaded models will be overwritten.

    Args:
        filename (): Name of the file to load.
        isolist (): Isolist applied to loaded models. If ``None`` no subselection is made.
        convert_unit (): Whether to convert units to the mole unit, as recommended, when creating the isolist.
        where (): String evaluation used to select which models to load.
        **where_kwargs (): Additional keyword arguments used together with ``where``.
    """
    logger.info(f'Loading file: {filename}')
    t0 = datetime.datetime.now()
    with h5py.File(filename, 'r') as efile:
        file_type = efile.attrs.get('FILE_TYPE', None)
        version = efile.attrs.get('VERSION', "-1")
        created = efile.attrs.get('CREATED', None)
        if file_type != "simple.ModelCollection":
            logger.warning(f'File {filename} is not a simple.ModelCollection file')
        if int(float(version)) != int(float(self.__version__)):
            logger.warning(f'File {filename} was created with ModelCollection v{version}, but this version of simple uses ModelCollection v{self.__version__}')

        for name, group in efile['refs'].items():
            ref = self._load_ref(group, name)
            self.add_ref(ref)

        for name, group in efile['models'].items():
            model = self._load_model(group, name, isolist, convert_unit, where, where_kwargs)
            self.add_model(model)

    t = datetime.datetime.now() - t0
    logger.info(f'Time to load file: {t}')

new_model

new_model(clsname, name, **attrs)

Create a new model and add it to the current collection.

Parameters:

  • clsname

    The name of the model class to be created.

  • name

    Name of the new model.

  • **attrs

    Attributes to be added to the new model.

Returns:

  • The newly created model.

Source code in simple/models.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
def new_model(self, clsname, name, **attrs):
    """
    Create a new model and add it to the current collection.

    Args:
        clsname (): The name of the model class to be created.
        name (): Name of the new model.
        **attrs (): Attributes to be added to the new model.

    Returns:
        The newly created model.
    """
    if clsname in AllModelClasses:
        model = AllModelClasses[clsname](name, reference_models_ = self.refs, **attrs)
        return self.add_model(model)
    else:
        raise ValueError(f"No model class called '{clsname}' exists")

new_ref

new_ref(clsname, name, **attrs)

Create a new reference model and add it to the current collection.

Parameters:

  • clsname

    The name of the model class to be created.

  • name

    Name of the new model.

  • **attrs

    Attributes to be added to the new model.

Returns:

  • The newly created reference model.

Source code in simple/models.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
def new_ref(self, clsname, name, **attrs):
    """
    Create a new reference model and add it to the current collection.

    Args:
        clsname (): The name of the model class to be created.
        name (): Name of the new model.
        **attrs (): Attributes to be added to the new model.

    Returns:
        The newly created reference model.
    """
    if clsname in AllModelClasses:
        ref = AllModelClasses[clsname](name, reference_models_=self.refs, **attrs)
        return self.add_ref(ref)
    else:
        raise ValueError(f"No model class called '{clsname}' exists")

save

save(filename)

Save the current selection of models.

Parameters:

  • filename

    Name of the file to be created.

Source code in simple/models.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def save(self, filename):
    """
    Save the current selection of models.

    Args:
        filename (): Name of the file to be created.
    """
    if filename[-5:].lower() != '.hdf5':
        filename += '.hdf5'

    t0 = datetime.datetime.now()
    with h5py.File(filename, 'w') as file:
        file.attrs['FILE_TYPE'] = "simple.ModelCollection"
        file.attrs['VERSION'] = self.__version__
        file.attrs['CREATED'] = datetime.datetime.now().isoformat()

        logger.info(f'Saving ModelCollection(v{self.__version__}) as: {filename}')

        ref_group = file.create_group('refs', track_order=True)
        for ref in self.refs:
            logger.info(f'saving ref: {ref.name}')
            self._save_model(ref_group, ref)

        model_group = file.create_group('models', track_order=True)
        for model in self.models:
            logger.info(f'saving model: {model.name}')
            self._save_model(model_group, model)

    t = datetime.datetime.now() - t0
    logger.info(f'Filesize: {os.path.getsize(filename) / 1024 / 1024:.2f} MB')
    logger.info(f'Time to save file: {t}')

select_isolist

select_isolist(isolist=None)

Used to create a subselection of data from each model.

Note The original array may be overwritten with the new subselection.

Parameters:

  • isolist

    Either a list of isotopes to be selected or a dictionary consisting of the

Raises:

  • NotImplementedError

    Raised if this method has not been implemented for a model class.

Source code in simple/models.py
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
def select_isolist(self, isolist=None):
    """
    Used to create a subselection of data from each model.

    **Note** The original array may be overwritten with the new subselection.

    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.

    Raises:
        NotImplementedError: Raised if this method has not been implemented for a model class.
    """
    for model in self.models:
        model.select_isolist(isolist)

standard_normalisation

standard_normalisation(normiso, enrichment_factor=1, relative_enrichment=True, convert_unit=True, attrname='stdnorm')

Normalise the appropriate data of the model. See [simple_normalisation][simple.norm.simple_normalisation] for a description of the procedure and a description of the arguments.

The result of the normalisation will be saved to each model under the normname attribute.

Raises:

  • NotImplementedError

    Raised if the data to be normalised has not been specified for this model class.

Source code in simple/models.py
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
def standard_normalisation(self, normiso, enrichment_factor=1, relative_enrichment=True,
                         convert_unit=True, attrname = 'stdnorm'):
    """
    Normalise the appropriate data of the model. See
    [simple_normalisation][simple.norm.simple_normalisation] for a description of the procedure and a
    description of the arguments.

    The result of the normalisation will be saved to each model under the ``normname`` attribute.

    Raises:
        NotImplementedError: Raised if the data to be normalised has not been specified for this model class.
    """
    for model in self.models:
        model.standard_normalisation(normiso, enrichment_factor=enrichment_factor,
                                     relative_enrichment=relative_enrichment,
                                     convert_unit=convert_unit, attrname=attrname)

where

where(where, **where_kwargs)

Returns a copy of the collection containing only the models which match the where argument.

Use & or | to combine multiple evaluations. To evaluate an attribute of each model put a dot before the name e.g. .mass == 15. To use one of the where_kwargs values put the name of the kwarg within pointy brackets e.g. .mass == {mass_kwarg}.

The available operators for evaluations are ==, !=, >, >=, <, <=, IN, and NOT IN.

Note that a shallow copy of the matching models is returned.

Parameters:

  • where

    A string with the evaluation to perform for each model.

  • **where_kwargs

    Arguments used for the evaluation.

Source code in simple/models.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
def where(self, where, **where_kwargs):
    """
    Returns a copy of the collection containing only the models which match the ``where`` argument.

    Use ``&`` or ``|`` to combine multiple evaluations. To evaluate an attribute of each model put a
    dot before the name e.g. ``.mass == 15``. To use one of the ``where_kwargs`` values put the name
    of the kwarg within pointy brackets e.g. ``.mass == {mass_kwarg}``.

    The available operators for evaluations are ``==``, ``!=``, ``>``, ``>=``, ``<``, ``<=``,
    `` IN ``, and `` NOT IN ``.

    **Note** that a shallow copy of the matching models is returned.

    Args:
        where (): A string with the evaluation to perform for each model.
        **where_kwargs (): Arguments used for the evaluation.
    """
    models = utils.models_where(self.models, where, **where_kwargs)

    new_collection = self.__class__()
    for model in models:
        new_collection.add_model(model)

    return new_collection

simple.models.load_collection

load_collection(filename, dbfilename=None, *, default_isolist=None, convert_unit=True, overwrite=False, where=None, **where_kwargs)

Loads a selection of models from a file.

If that file does not exist it will create the file from the specified models file. Only when doing this is the default_isolist applied. If filename already exits the assumption is it has the correct isolist.

*Notes

The entire file will be read into memory. This might be an issue if reading very large files. The hdf5 are compressed so will be significantly larger when stored in memory.

When reading the database file to create a subselection of the data using default_isolist, the subselection is made when each model is loaded which reduces the amount of memory used.

Parameters:

  • filename (str) –

    Name of the file to load or create.

  • dbfilename (str, default: None ) –

    Name of the _func models file

  • default_isolist

    Isolist applied to loaded models from dbfilename.

  • convert_units (bool) –

    If True and data is stored in a mass unit all values will be divided by the mass number of the isotope before summing values together. The final value is then multiplied by the mass number of the output isotope.

  • overwrite (bool, default: False ) –

    If True a new file will be created even if filename already exists.

  • where (str, default: None ) –

    Used to select which models to load.

  • **where_kwargs

    Keyword arguments used in combination with where.

Returns:

Source code in simple/models.py
117
118
119
120
121
122
123
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
def load_collection(filename, dbfilename=None, *, default_isolist=None, convert_unit=True, overwrite=False,
                    where=None, **where_kwargs):
    """
    Loads a selection of models from a file.

    If that file does not exist it will create the file from the specified models file. Only when doing this
    is the ``default_isolist`` applied. If ``filename`` already exits the **assumption** is it has the correct isolist.

    ***Notes**

    The entire file will be read into memory. This might be an issue if reading very large files. The hdf5 are
    compressed so will be significantly larger when stored in memory.

    When reading the database file to create a subselection of the data using ``default_isolist``, the subselection is
     made when each model is loaded which reduces the amount of memory used.

    Args:
        filename (str): Name of the file to load or create.
        dbfilename (str): Name of the _func models file
        default_isolist (): Isolist applied to loaded models from ``dbfilename``.
        convert_units (bool): If ``True``  and data is stored in a mass unit all values will be divided by the
            mass number of the isotope before summing values together. The final value is then multiplied by the
            mass number of the output isotope.
        overwrite (bool): If ``True`` a new file will be created even if ``filename`` already exists.
        where (str): Used to select which models to load.
        **where_kwargs (): Keyword arguments used in combination with ``where``.

    Returns:
        A [ModelCollection][simple.models.ModelCollection] object containing all the loaded models.
    """
    mc = ModelCollection()
    if os.path.exists(filename) and not overwrite:
        logger.info(f'Loading existing file: {filename}')
        mc.load_file(filename, where=where, **where_kwargs)
    elif filename[-5:].lower() != '.hdf5' and os.path.exists(f'{filename}.hdf5') and not overwrite:
        logger.info(f'Loading existing file: {filename}.hdf5')
        mc.load_file(f'{filename}.hdf5', where=where, **where_kwargs)
    elif dbfilename is None:
        raise ValueError(f'File {filename} does not exist')
    elif os.path.exists(dbfilename):
        logger.info(f'Creating: "{filename}" from database: "{dbfilename}"')
        mc.load_file(dbfilename, isolist=default_isolist, convert_unit=convert_unit, where=where, **where_kwargs)
        mc.save(filename)
    else:
        raise ValueError(f'Neither "{filename}" or "{dbfilename}" exist')
    return mc

simple.models.load_csv_h

load_csv_h(filename)

Returns a key array from a csv file where the first from is the columns keys and the remaining rows contain the data.

Source code in simple/models.py
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
def load_csv_h(filename):
    """
    Returns a key array from a csv file where the first from is the columns keys and the remaining
    rows contain the data.
    """
    with open(filename, 'r') as csvfile:
        reader = csv.reader(csvfile, skipinitialspace=True)
        data = [row for row in reader if row[0][0] != '#']

    return np.transpose([float(i) for i in data[1]]), utils.asisotopes(data[0], allow_invalid=True)

simple.models.load_models

load_models(*args, **kwargs)
Source code in simple/models.py
164
165
166
def load_models(*args, **kwargs):
    # Keeps for legacy reasons. Use load_collection instead
    return load_collection(*args, **kwargs)

simple.models.load_ppn

load_ppn(filename)

Load a key array from a ppn file.

Source code in simple/models.py
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
def load_ppn(filename):
    """
    Load a key array from a ppn file.
    """
    isotopes = []
    values = []
    with open(filename, 'r') as f:
        for row in f.readlines():
            isotopes.append(row[3:9].replace(' ', ''))
            values.append(float(row[10:].strip()))

    return np.transpose(values), utils.asisotopes(isotopes, allow_invalid=True)

simple.models.new_collection

new_collection()

Return an empty ModelCollection object.

Source code in simple/models.py
168
169
170
171
172
def new_collection():
    """
    Return an empty [ModelCollection][simple.models.ModelCollection] object.
    """
    return ModelCollection()