Skip to content

simple.models

simple.models.AllModelClasses module-attribute

AllModelClasses = {}

A dictionary containing all the avaliable model classes.

When a new model class is created subclassing ModelTemplate 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)
Source code in simple/utils.py
 99
100
def __init__(self, *args, **kwargs):
    self.update(*args, **kwargs)

get

get(key, value, default=None)
Source code in simple/models.py
31
32
33
34
35
def get(self, key, value, default=None):
    if key in self:
        return self[key]
    else:
        return utils.asarray(default)

simple.models.IsoRef

IsoRef(collection, name, **hdf5_attrs)

Bases: ModelTemplate

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

Source code in simple/models.py
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
def __init__(self, collection, name, **hdf5_attrs):
    super().__setattr__('collection', collection)
    super().__setattr__('name', name)
    super().__setattr__('hdf5_attrs', HDF5Dict())
    super().__setattr__('normal_attrs', NamedDict())

    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)


    if self.ISREF:
        self.collection.refs[self.name] = self
    else:
        self.collection.models[self.name] = self

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.ModelCollection

ModelCollection()

The main interface for working with a collection of models.

Source code in simple/models.py
120
121
122
def __init__(self):
    self.refs = {}
    self.models = {}

models instance-attribute

models = {}

refs instance-attribute

refs = {}

copy

copy()

Returns a new collection containing a shallow copy of all the models in the current collection.

Source code in simple/models.py
344
345
346
347
348
349
350
351
352
def copy(self):
    """
    Returns a new collection containing a shallow copy of all the models in the current collection.
    """
    new_collection = self.__class__()
    for model in self.models.values():
        model.copy_to(new_collection)

    return new_collection

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 named model is returned instead.

Source code in simple/models.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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 named model is returned instead.
    """
    if name in self.models:
        model = self.models[name]
    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 named model is returned instead.

Source code in simple/models.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
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 named model is returned instead.
    """
    if name in self.refs:
        ref = self.refs[name]
    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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
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.values():
        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.

  • 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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.
        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:
        for name, group in efile['models'].items():
            model = self._load_model(efile, group, name, isolist, convert_unit, where, where_kwargs)
            #self.models[name] = 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.

Note if a model already exists called name it will be overwritten.

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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def new_model(self, clsname, name, **attrs):
    """
    Create a new model and add it to the current collection.

    **Note** if a model already exists called ``name`` it will be overwritten.

    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:
        return AllModelClasses[clsname](self, name, **attrs)
    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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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:
        ref_group = file.create_group('ref', track_order=True)
        for name, ref in self.refs.items():
            self._save_model(ref_group, ref)

        model_group = file.create_group('models', track_order=True)
        for name, model in self.models.items():
            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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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.values():
        model.select_isolist(isolist)

simple_normalisation

simple_normalisation(*args, **kwargs)

Deprecated Method. Use standard_normalisation instead.

Source code in simple/models.py
391
392
393
394
395
396
@utils.deprecation_warning('``simple_normalisation`` has been deprecated. Use ``standard_normalisation`` instead')
def simple_normalisation(self, *args, **kwargs):
    """
    Deprecated Method. Use ``standard_normalisation`` instead.
    """
    return self.standard_normalisation(*args, **kwargs)

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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
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.values():
        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.

Returns:

  • bool

Source code in simple/models.py
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
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.

    Returns:
        bool

    """
    eval = utils.simple_eval.parse_where(where)
    new_collection = self.__class__()
    for model in self.models.values():
        if eval.eval(model, where_kwargs):
            model.copy_to(new_collection)

    return new_collection

simple.models.ModelTemplate

ModelTemplate(collection, name, **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.
  • ISREF - Should be True for models specifically for storing reference values. These will be stored in the ModelCollection.refs dictionary rather than ModelCollection.models dictionary.
Source code in simple/models.py
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
def __init__(self, collection, name, **hdf5_attrs):
    super().__setattr__('collection', collection)
    super().__setattr__('name', name)
    super().__setattr__('hdf5_attrs', HDF5Dict())
    super().__setattr__('normal_attrs', NamedDict())

    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)


    if self.ISREF:
        self.collection.refs[self.name] = self
    else:
        self.collection.models[self.name] = self

ABUNDANCE_KEYARRAY class-attribute instance-attribute

ABUNDANCE_KEYARRAY = None

ISREF class-attribute instance-attribute

ISREF = False

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

change_name

change_name(name)

Change the name of the current model to name.

Note if another model already exists with this name in the collection it will be replaced with this model.

Source code in simple/models.py
541
542
543
544
545
546
547
548
549
def change_name(self, name):
    """
    Change the name of the current model  to ``name``.

    **Note** if another model already exists with this name in the collection it will be replaced with this model.
    """
    self.collection.models.pop(self.name)
    super().__setattr__('name', name)
    self.collection.models[self.name] = self

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

    The unit the array should be converted to. If None no conversion is made

Raises:

  • ValueError

    If the array cannot be converted from unit to desired_unit_args.

Returns:

  • A copy of array with the desired unit.

Source code in simple/models.py
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
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
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.

    Raises:
        ValueError: If the array cannot be converted from ``unit`` to ``desired_unit_args``.

    Returns:
        A copy of ``array`` with the desired unit.
    """
    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}'")

copy_to

copy_to(collection)

Create a shallow copy of the current model in collection.

Returns:

  • The new model

Source code in simple/models.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
def copy_to(self, collection):
    """
    Create a shallow copy of the current model in ``collection``.

    Returns:
        The new model
    """
    new_model = self.__class__(collection, self.name, **self.hdf5_attrs)

    for k, v in self.hdf5_attrs.items():
        # Make sure ref are in new models object
        if k[:6] == 'refid_':
            if type(v) is str and v not in collection.refs:
                collection.refs[v] = self.collection.refs[v]

    for k, v in self.normal_attrs.items():
        new_model.setattr(k, v, hdf5_compatible=False, overwrite=True)

    return new_model

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
691
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
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)
Source code in simple/models.py
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
764
765
766
767
768
769
770
771
772
def get_array_labels(self, name=None, latex=True):
    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
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
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
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(mask_attrs, mask, shape, **mask_attrs)

get_ref

get_ref(name, attr=None)

Returns the reference model from the parent collection with the given name.

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

Source code in simple/models.py
533
534
535
536
537
538
539
def get_ref(self, name, attr=None):
    """
    Returns the reference model from the parent collection with the given name.

    If ``attr`` is given then the value of that attribute from the named model is returned instead.
    """
    return self.collection.get_ref(name, 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 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
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
854
855
856
857
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
    ref_stdmass = self.get_ref(self.refid_isomass)
    stdmass, stdmass_unit = ref_stdmass.get_array('data')

    # The reference abundances. Typically, the initial values of the model
    ref_stdabu = self.get_ref(self.refid_isoabu)
    stdabu, stdabu_unit = ref_stdabu.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
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
810
811
812
813
814
815
816
817
818
819
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
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
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
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
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)

    ref_stdabu = self.get_ref(self.refid_isoabu)
    stdabu, stdabu_unit = ref_stdabu.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.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 raw 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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 raw 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
910
911
912
913
914
915
916
917
918
919
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
87
88
89
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
922
923
924
925
926
927
928
929
930
931
932
933
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
91
92
93
94
95
def new_collection():
    """
    Return an empty [ModelCollection][simple.models.ModelCollection] object.
    """
    return ModelCollection()