cgohlke / molmass

Molecular mass calculations
https://pypi.org/project/molmass
BSD 3-Clause "New" or "Revised" License
56 stars 13 forks source link

Add possibility to describe ions with a Formula object #5

Closed n-elie closed 1 year ago

n-elie commented 3 years ago

This PR adds the possibility to add charges to formula objects, like this:

>>> Formula("C5H14NO+")
Formula('[C5H14NO]+')
>>> Formula("C5H14NO+").charge
1
>>> def _(spectrum):
         max_val = max([val[1] for val in spectrum.values()])
         for key, val in spectrum.items():
             print(f'{key}, {val[0]:.6f}, {val[1]/max_val*100:.6f}%')
>>> _(Formula("C5H14NO+").spectrum())
104, 104.106990, 100.000000%
105, 105.110043, 5.972305%
106, 106.111988, 0.353913%
107, 107.114511, 0.014170%
108, 108.117331, 0.000315%
109, 109.120178, 0.000004%
110, 110.123193, 0.000000%
>>> Formula("SO4_2-")
Formula('[SO4]2-')
>>> Formula('[SO4]2-').charge
-2
>>> _(Formula("SO4_2-").spectrum())
96, 47.976413, 100.000000%
97, 48.476498, 0.941927%
98, 48.974968, 5.297443%
99, 49.477523, 0.014247%
100, 49.976118, 0.049850%
101, 50.478593, 0.000080%
102, 50.977559, 0.000203%
103, 51.479728, 0.000000%
104, 51.979089, 0.000000%
105, 52.480272, 0.000000%
106, 52.980286, 0.000000%
cgohlke commented 3 years ago

Excuse my ignorance. What is H++ and why is the mass 0.5034? Why is the adjusted mass divided by abs(self.charge)?

n-elie commented 3 years ago

You are right, an H2+ does not make any sense. This was a very bad example. I edited the tests for this attribute.

The idea behind this PR is to use it for mass spectrometry applications. The adjusted mass is divided by abs(self.charge) because this is a mass-to-charge ratio, noted m/z where mis the mass and z is the number of charge.

I renamed the ion_mass attribute to mz to make this clearer. I also added a charge attribute and an mz property to the Isotope class to make sure the mass attribute still return the same value than before. Maybe the monoisotopic mass should also be corrected by the electron mass. This would make sense (does not really matter for mean mass but it could be done).

I also added an mz_spectrum attribute to the Formula object. This will return an m/z spectrum with mz ratios and percentage of maximum intensity instead of masses and fractions, as this the common way to describe an m/z spectrum.

Would you be interested by these modifications if I can make things cleaner and backward compatible?

cgohlke commented 1 year ago

Thank you, and sorry for taking so long. This functionality has been integrated in v2022.10.18 .

>>> from molmass import Formula
>>> Formula("C5H14NO+")
Formula('[C5H14NO]+')
>>> Formula("C5H14NO+").charge
1
>>> print(Formula("C5H14NO+").spectrum())
A    Relative mass  Fraction %  Intensity %
105      105.11004    5.616198     5.972305
106      106.11199    0.332810     0.353913
107      107.11451    0.013325     0.014170
108      108.11733    0.000297     0.000315
109      109.12017    0.000004     0.000004
110      110.12294    0.000000     0.000000
111      111.12567    0.000000     0.000000
112      112.12866    0.000000     0.000000
113      113.13330    0.000000     0.000000
>>> Formula("SO4_2-")
Formula('[SO4]2-')
>>> Formula('[SO4]2-').charge
-2
>>> print(Formula("SO4_2-").spectrum())
A    Relative mass  Fraction %  Intensity %  m/z
96       95.952827   94.070057   100.000000  47.976413
97       96.952996    0.886071     0.941927  48.476498
98       97.949936    4.983307     5.297443  48.974968
99       98.955046    0.013403     0.014247  49.477523
100      99.952236    0.046894     0.049850  49.976118
101     100.957185    0.000075     0.000080  50.478593
102     101.955117    0.000191     0.000203  50.977559
103     102.959521    0.000000     0.000000  51.479760
104     103.958227    0.000000     0.000000  51.979114
105     104.962061    0.000000     0.000000  52.481030
106     105.961473    0.000000     0.000000  52.980737
107     106.964788    0.000000     0.000000  53.482394
108     107.964816    0.000000     0.000000  53.982408
n-elie commented 1 year ago

Good to hear, I will have a look to this soon Thanks for your work