package com.compomics.util.experiment.identification.matches; import com.compomics.util.experiment.biology.Atom; import com.compomics.util.experiment.biology.Ion; import com.compomics.util.experiment.biology.ions.*; import com.compomics.util.experiment.identification.spectrum_annotation.IonMatchKeysCache; import com.compomics.util.experiment.massspectrometry.Charge; import com.compomics.util.experiment.massspectrometry.Peak; import com.compomics.util.experiment.personalization.ExperimentObject; import com.compomics.util.pride.CvTerm; /** * This class represents the assignment of a peak to a theoretical ion. * * @author Marc Vaudel */ public class IonMatch extends ExperimentObject { /** * The version UID for serialization/deserialization compatibility. */ static final long serialVersionUID = 5753142782728884464L; /** * The matched peak. */ public Peak peak; /** * The matching ion. */ public Ion ion; /** * The inferred charge of the ion. */ public Integer charge; /** * The sign of the charge. */ public Integer chargeSign = Charge.PLUS; /** * Constructor for an ion peak. * * @param aPeak the matched peak * @param anIon the corresponding type of ion * @param charge the inferred charge of the ion */ public IonMatch(Peak aPeak, Ion anIon, Integer charge) { peak = aPeak; ion = anIon; this.charge = charge; } /** * Get the absolute matching error in Da. * * @return the absolute matching error */ public double getAbsoluteError() { double theoreticMz = ion.getTheoreticMz(charge); return peak.mz - theoreticMz; } /** * Get the absolute matching error in Da after isotope removal. * * @param minIsotope the minimal isotope * @param maxIsotope the maximal isotope * * @return the absolute matching error */ public double getAbsoluteError(int minIsotope, int maxIsotope) { double theoreticMz = ion.getTheoreticMz(charge); double measuredMz = peak.mz; measuredMz -= getIsotopeNumber(minIsotope, maxIsotope) * Atom.C.getDifferenceToMonoisotopic(1) / charge; return measuredMz - theoreticMz; } /** * Get the relative m/z matching error in ppm. * * @return the relative matching error */ public double getRelativeError() { double theoreticMz = ion.getTheoreticMz(charge); double measuredMz = peak.mz; return ((measuredMz - theoreticMz) * 1000000) / theoreticMz; } /** * Get the relative m/z matching error in ppm after isotope removal. * * @param minIsotope the minimal isotope * @param maxIsotope the maximal isotope * * @return the relative matching error */ public double getRelativeError(int minIsotope, int maxIsotope) { double theoreticMz = ion.getTheoreticMz(charge); double measuredMz = peak.mz; measuredMz -= getIsotopeNumber(minIsotope, maxIsotope) * Atom.C.getDifferenceToMonoisotopic(1) / charge; return ((measuredMz - theoreticMz) * 1000000) / theoreticMz; } /** * Returns the distance in number of neutrons between the experimental mass * and theoretic mass, image of the isotope number: 1 typically indicates * C13 isotope. * * @param minIsotope the minimal isotope * @param maxIsotope the maximal isotope * * @return the distance in number of neutrons between the experimental mass * and theoretic mass */ public int getIsotopeNumber(int minIsotope, int maxIsotope) { double experimentalMass = peak.mz * charge - charge * ElementaryIon.proton.getTheoreticMass(); double result = (experimentalMass - ion.getTheoreticMass()) / Atom.C.getDifferenceToMonoisotopic(1); return Math.min(Math.max((int) Math.round(result), minIsotope), maxIsotope); } /** * Returns the error. * * @param isPpm a boolean indicating whether the error should be retrieved * in ppm (true) or in Dalton (false) * @param minIsotope the minimal isotope * @param maxIsotope the maximal isotope * * @return the match m/z error */ public double getError(boolean isPpm, int minIsotope, int maxIsotope) { if (isPpm) { return getRelativeError(minIsotope, maxIsotope); } else { return getAbsoluteError(minIsotope, maxIsotope); } } /** * Returns the error. * * @param isPpm a boolean indicating whether the error should be retrieved * in ppm (true) or in Dalton (false) * * @return the match m/z error */ public double getError(boolean isPpm) { if (isPpm) { return getRelativeError(); } else { return getAbsoluteError(); } } /** * Returns the annotation to use for the ion match as a String. * * @return the annotation to use for the given ion match */ public String getPeakAnnotation() { return getPeakAnnotation(false, ion, new Charge(chargeSign, charge)); } /** * Returns the annotation to use for a given ion and charge as a String. * * @param ion the given ion * @param charge the given charge * @return the annotation to use for the given ion match */ public static String getPeakAnnotation(Ion ion, Charge charge) { return getPeakAnnotation(false, ion, charge); } /** * Returns the key for the ion match uniquely representing a peak * annotation. * * @param ion the ion matched * @param charge the charge * * @return the key for the ion match */ public static String getMatchKey(Ion ion, int charge) { return getMatchKey(ion, charge, null); } /** * Returns the key for the ion match uniquely representing a peak * annotation. If a cache is given it will be used to store keys, ignored if * null. * * @param ion the ion matched * @param charge the charge * @param ionMatchKeysCache a cache for the ion match keys * * @return the key for the ion match */ public static String getMatchKey(Ion ion, int charge, IonMatchKeysCache ionMatchKeysCache) { if (ionMatchKeysCache != null) { return ionMatchKeysCache.getMatchKey(ion, charge); } Ion.IonType ionType = ion.getType(); int ionTypeIndex = ionType.index; int ionSubType = ion.getSubType(); int fragmentIonNumber; if (ionType == Ion.IonType.PEPTIDE_FRAGMENT_ION) { PeptideFragmentIon fragmentIon = ((PeptideFragmentIon) ion); fragmentIonNumber = fragmentIon.getNumber(); } else if (ionType == Ion.IonType.TAG_FRAGMENT_ION) { TagFragmentIon tagFragmentIon = ((TagFragmentIon) ion); fragmentIonNumber = tagFragmentIon.getNumber(); } else { fragmentIonNumber = 0; } String neutralLossesAsString = ion.getNeutralLossesAsString(); String key = getMatchKey(ionTypeIndex, ionSubType, fragmentIonNumber, neutralLossesAsString, charge); return key; } /** * Returns the key based on the different attributes of a match. * * @param ionTypeIndex the index of the ion type * @param ionSubType the index of the ion subtype * @param fragmentIonNumber the number of the ion, 0 if none * @param neutralLossesAsString the neutral losses as a string * @param charge the charge * * @return the key for the ion match */ public static String getMatchKey(int ionTypeIndex, int ionSubType, int fragmentIonNumber, String neutralLossesAsString, int charge) { StringBuilder stringBuilder = new StringBuilder(8); stringBuilder.append(ionTypeIndex).append("_").append(ionSubType).append("_").append(fragmentIonNumber).append("_").append(neutralLossesAsString).append("_").append(charge); return stringBuilder.toString(); } /** * Returns the annotation to use for a given ion and charge as a String. * * @param html if true, returns the annotation as HTML with subscripts tags * @param ion the given ion * @param charge the given charge * @return the annotation to use for the given ion match */ public static String getPeakAnnotation(boolean html, Ion ion, Charge charge) { StringBuilder result = new StringBuilder(); switch (ion.getType()) { case PEPTIDE_FRAGMENT_ION: if (html) { result.append("<html>"); } result.append(ion.getSubTypeAsString()); // add fragment ion number PeptideFragmentIon fragmentIon = ((PeptideFragmentIon) ion); if (html) { result.append("<sub>").append(fragmentIon.getNumber()).append("</sub>"); } else { result.append(fragmentIon.getNumber()); } // add charge result.append(charge.getChargeAsFormattedString()); // add any neutral losses if (html) { String neutralLoss = ion.getNeutralLossesAsString(); for (int i = 0; i < neutralLoss.length(); i++) { if (Character.isDigit(neutralLoss.charAt(i))) { result.append("<sub>").append(neutralLoss.charAt(i)).append("</sub>"); } else { result.append(neutralLoss.charAt(i)); } } } else { result.append(ion.getNeutralLossesAsString()); } if (html) { result.append("</html>"); } return result.toString(); case TAG_FRAGMENT_ION: TagFragmentIon tagFragmentIon = (TagFragmentIon) ion; if (html) { result.append("<html>"); } // add type result.append(ion.getSubTypeAsString()); // add fragment ion number if (html) { result.append("<sub>").append(tagFragmentIon.getSubNumber()).append("</sub>"); } else { result.append(tagFragmentIon.getSubNumber()); } // add charge result.append(charge.getChargeAsFormattedString()); // add any neutral losses if (html) { String neutralLoss = ion.getNeutralLossesAsString(); for (int i = 0; i < neutralLoss.length(); i++) { if (Character.isDigit(neutralLoss.charAt(i))) { result.append("<sub>").append(neutralLoss.charAt(i)).append("</sub>"); } else { result.append(neutralLoss.charAt(i)); } } } else { result.append(ion.getNeutralLossesAsString()); } if (html) { result.append("</html>"); } return result.toString(); case PRECURSOR_ION: if (html) { result.append("<html>"); } result.append(ion.getSubTypeAsString()).append("-"); // add charge result.append(charge.getChargeAsFormattedString()); // add any neutral losses String neutralLoss = ion.getNeutralLossesAsString(); if (html) { for (int i = 0; i < neutralLoss.length(); i++) { if (Character.isDigit(neutralLoss.charAt(i))) { result.append("<sub>").append(neutralLoss.charAt(i)).append("</sub>"); } else { result.append(neutralLoss.charAt(i)); } } } else { result.append(neutralLoss); } if (html) { result.append("</html>"); } return result.toString(); default: if (html) { result.append("<html>"); } result.append(ion.getName()); if (html) { result.append("</html>"); } return result.toString(); } } /** * Returns the annotation to use for the given ion match as a String. * * @param html if true, returns the annotation as HTML with subscripts tags * @return the annotation to use for the given ion match */ public String getPeakAnnotation(boolean html) { return getPeakAnnotation(html, ion, new Charge(chargeSign, charge)); } /** * Returns the pride CV term for the ion match m/z. * * @return the pride CV term for the ion match m/z */ public CvTerm getMZPrideCvTerm() { return new CvTerm("PRIDE", "PRIDE:0000188", "product ion m/z", peak.mz + ""); } /** * Returns the pride CV term for the ion match intensity. * * @return the pride CV term for the ion match intensity */ public CvTerm getIntensityPrideCvTerm() { return new CvTerm("PRIDE", "PRIDE:0000189", "product ion intensity", peak.intensity + ""); } /** * Returns the pride CV term for the ion match error. * * @param minIsotope the minimal isotope * @param maxIsotope the maximal isotope * * @return the pride CV term for the ion match error */ public CvTerm getIonMassErrorPrideCvTerm(int minIsotope, int maxIsotope) { return new CvTerm("PRIDE", "PRIDE:0000190", "product ion mass error", getAbsoluteError(minIsotope, maxIsotope) + ""); } /** * Returns the pride CV term for the ion match charge. * * @return the pride CV term for the ion match charge */ public CvTerm getChargePrideCvTerm() { return new CvTerm("PRIDE", "PRIDE:0000204", "product ion charge", charge + ""); } /** * Enum of the supported error types. */ public enum MzErrorType { Absolute("Absolute", "Absolute error", "m/z"), RelativePpm("Relative (ppm)", "Relative error in ppm", "ppm"), Statistical("Statistical", "Probability to reach this error according to the error distribution", "%p"); /** * The name of the error type. */ public final String name; /** * The description of the error type. */ public final String description; /** * The unit to use */ public final String unit; /** * Constructor. * * @param name the name of the error type * @param description the description of the error type * @param unit the unit to use */ private MzErrorType(String name, String description, String unit) { this.name = name; this.description = description; this.unit = unit; } /** * Returns the error type corresponding to the given index. Error types * are indexed according to the values() method. Null if not found. * * @param index the index of the error type in the values() method * * @return the corresponding error type */ public static MzErrorType getMzErrorType(int index) { MzErrorType[] values = MzErrorType.values(); if (index >= 0 && index < values.length) { return values[index]; } return null; } } }