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) in ----> 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) in ----> 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()``.