Source code for paftacular.comps.base

"""Base classes and protocols for mzPAF components"""

from abc import ABC, abstractmethod
from collections import Counter

from tacular import ElementInfo


[docs] class Serializable(ABC): """Base class for serializable objects"""
[docs] @abstractmethod def serialize(self) -> str: pass
def __str__(self) -> str: return self.serialize()
[docs] class MassProvider(ABC): """Base class for objects that can provide mass"""
[docs] @abstractmethod def mass(self, monoisotopic: bool = True) -> float: """Calculate mass""" pass
@property def monoisotopic_mass(self) -> float: return self.mass(monoisotopic=True) @property def average_mass(self) -> float: return self.mass(monoisotopic=False)
[docs] class CompositionProvider(ABC): """Base class for objects that can provide composition""" @property @abstractmethod def composition(self) -> Counter["ElementInfo"]: """Get elemental composition""" pass @property def dict_composition(self) -> dict[str, int]: """Get composition as a dictionary with element symbols as keys""" return {str(elem): count for elem, count in self.composition.items()}
[docs] def mass(self, monoisotopic: bool = True) -> float: """Calculate mass from composition""" m = 0.0 for elem, count in self.composition.items(): m += elem.get_mass(monoisotopic) * count return m
[docs] class ScalableComposition(CompositionProvider): """Mixin for compositions that scale by count and sign""" count: int @property @abstractmethod def _single_composition(self) -> Counter["ElementInfo"]: """Get composition for single instance (before scaling)""" pass @property def composition(self) -> Counter["ElementInfo"]: """Get scaled composition""" return Counter({elem: count * self.count for elem, count in self._single_composition.items()}) @property def _sign_prefix(self) -> str: """Get sign prefix for serialization""" sign_str = "+" if self.count > 0 else "-" count_str = "" if abs(self.count) == 1 else str(abs(self.count)) return f"{sign_str}{count_str}"