Ranging data

Where Roi is the central container for positional data, RangeCollection is the central container for mass/charge ranging data. This contains the range data in an accessible format for both the user and for APAV facilities that use mass ranging. The RangeCollection container manages many (or few) Range objects for each range.

A single mass/charge range

The Range object defines a single half-open interval on the mass spectrum, and assigns it an Ion.

Note

All ranging activities in APAV involving an interval on the mass spectrum, take the interval as a half-open interval. If the interval goes from mass m0 to mass m1, then the interval is [m0, m1) where m = m0 is included but m = m1 is not.

To create a range for O2 on the interval [31.5, 33):

>>> import apav as ap
>>> O2 = ap.Range("O2", (31.5, 33))
>>> print(O2)
Range: O2, Min: 31.5, Max: 33, Vol: 1, Color: (0, 0, 0)

The lower and upper limits can be modified:

>>> O2.lower = 31
>>> print(O2)
Range: O2, Min: 31, Max: 33, Vol: 1, Color: (0, 0, 0)

But the lower value cannot be set equal, or above the upper value and vice versa:

>>> O2.lower = 40
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-11-fdbbc8818585> in <module>
----> 1 o2.lower = 40

~\PycharmProjects\apav\apav\core\range.py in lower(self, new)
     85         validate.positive_number(new)
     86         if new >= self._upper:
---> 87             raise ValueError(f"Lower bound for {self.composition} ({new}) cannot be >= upper bound ({self.upper})")
     88         self._lower = new
     89

ValueError: Lower bound for O2 (40) cannot be >= upper bound (33)

The interval on the mass spectrum can be retrieved:

>>> O2.interval
(31, 33)

We can test whether or not 2 ranges intersect:

>>> O2 = ap.Range("O2", (31.5, 33)
>>> Cu = ap.Range("Cu", (30, 31))
>>> O2.intersects(Cu)
False

>>> Cu2 = ap.Range("Cu", (30, 32))
>>> O2.intersects(Cu2)
True

Remembering a Range interval is half-open:

>>> O2 = ap.Range("O2", (31.5, 33)
>>> Cu = ap.Range("Cu", (30, 31.5))
>>> O2.intersects(Cu)
False

Other properties can be found in the API reference.

Collections of ranges

The RangeCollection is responsible for containing and operating on a collection of Range instances. Typically, this class is constructed from a file via RangeCollection.from_rng() or RangeCollection.from_rrng() for loading data from common *.RNG or *.RRNG range files from IVAS or other software into a RangedCollection.

Note

The ranged mass spectrum can be visualized using apav.analysis.RangedMassSpectrum

The RangeCollection can be constructed without any Range, an empty RangeCollection can be initialized as simply:

>>> import apav as ap
>>> rng_col = ap.RangedCollection()

Then multiple Range objects can be added:

>>> rng_col.add(ap.Range("Cu", (30, 31.5)))
>>> rng_col.add(ap.Range("O2", (31.5, 32)))
>>> rng_col.add(ap.Range("Cu", (32, 33)))
>>> print(rng_col)
RangeCollection
Number of ranges: 3
Mass range: 30 - 33
Number of unique elements: 2
Elements: ('Cu', 'O')

Composition      Min (Da)    Max (Da)    Volume  Color (RGB 0-1)
-------------  ----------  ----------  --------  -----------------
Cu                   30          31.5         1  (0, 0, 0)
O2                   31.5        32           1  (0, 0, 0)
Cu                   32          33           1  (0, 0, 0)

Alternatively the Range objects can be passed into the constructor:

>>> rngs = [ap.Range("Cu", (30, 31.5)),
            ap.Range("O2", (31.5, 32)),
            ap.Range("Cu", (32, 33))]
>>> rng_col = ap.RangeCollection(rngs)

There cannot be overlapping ranges in a RangeCollection:

>>> rng_col.add(ap.Range("O2", (32.5, 34)))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-bc12e9d521c5> in <module>
----> 1 rng_col.add(ap.Range("O2", (32.5, 34)))

~\PycharmProjects\apav\apav\core\range.py in add(self, new)
    315             for r in self.ranges:
    316                 if r.intersects(new):
--> 317                     raise ValueError("Mass ranges cannot coincide")
    318             self._ranges.append(new)
    319

ValueError: Mass ranges cannot coincide

If a mass Range needs to be modified, it can be removed and a new range added. Modifying the first copper range to start at 28:

>>> rng_col.remove_by_mass(30)
>>> rng_col.add(ap.Range("Cu", (28, 31.5)))
RangeCollection
Number of ranges: 3
Mass range: 28 - 33
Number of unique elements: 2
Elements: O, Cu

Composition      Min (Da)    Max (Da)    Volume  Color (RGB 0-1)
-------------  ----------  ----------  --------  -----------------
Cu                   28          31.5         1  (0, 0, 0)
O2                   31.5        32           1  (0, 0, 0)
Cu                   32          33           1  (0, 0, 0)

Or see RangeCollection.replace().