1 /* Copyright (C) 2001-2007 Christoph Steinbeck <steinbeck@users.sf.net> 2 * 2013 Egon Willighagen <egonw@users.sf.net> 3 * 4 * Contact: cdk-devel@lists.sourceforge.net 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public License 8 * as published by the Free Software Foundation; either version 2.1 9 * of the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 19 */ 20 package org.openscience.cdk.config; 21 22 import org.openscience.cdk.interfaces.IAtom; 23 import org.openscience.cdk.interfaces.IAtomContainer; 24 import org.openscience.cdk.interfaces.IElement; 25 import org.openscience.cdk.interfaces.IIsotope; 26 import org.openscience.cdk.tools.ILoggingTool; 27 import org.openscience.cdk.tools.LoggingToolFactory; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Used to store and return data of a particular isotope. The classes 34 * {@link Isotopes} extends this class and is to be used to get isotope 35 * information. 36 * 37 * @cdk.module core 38 * @cdk.githash 39 * 40 * @author steinbeck 41 * @cdk.created 2001-08-29 42 */ 43 public abstract class IsotopeFactory { 44 45 public static final IIsotope[] EMPTY_ISOTOPE_ARRAY = new IIsotope[0]; 46 @SuppressWarnings("unchecked") 47 private List<IIsotope> isotopes[] = new List[256]; 48 @SuppressWarnings("unchecked") 49 private IIsotope majorIsotope[] = new IIsotope[256]; 50 protected static ILoggingTool logger = LoggingToolFactory.createLoggingTool(IsotopeFactory.class); 51 52 /** 53 * Returns the number of isotopes defined by this class. 54 * 55 *@return The size value 56 */ getSize()57 public int getSize() { 58 int count = 0; 59 for (List<IIsotope> isotope : isotopes) 60 if (isotope != null) 61 count += isotope.size(); 62 return count; 63 } 64 65 /** 66 * Protected methods only to be used by classes extending this class to add 67 * an IIsotope. 68 */ add(IIsotope isotope)69 protected void add(IIsotope isotope) { 70 Integer atomicNum = isotope.getAtomicNumber(); 71 assert atomicNum != null; 72 List<IIsotope> isotopesForElement = isotopes[atomicNum]; 73 if (isotopesForElement == null) { 74 isotopesForElement = new ArrayList<>(); 75 isotopes[atomicNum] = isotopesForElement; 76 } 77 isotopesForElement.add(isotope); 78 } 79 80 /** 81 * Gets an array of all isotopes known to the IsotopeFactory for the given 82 * element symbol. 83 * 84 * @param elem atomic number 85 * @return An array of isotopes that matches the given element symbol 86 */ getIsotopes(int elem)87 public IIsotope[] getIsotopes(int elem) { 88 if (isotopes[elem] == null) 89 return EMPTY_ISOTOPE_ARRAY; 90 List<IIsotope> list = new ArrayList<>(); 91 for (IIsotope isotope : isotopes[elem]) { 92 list.add(clone(isotope)); 93 } 94 return list.toArray(new IIsotope[0]); 95 } 96 97 /** 98 * Gets an array of all isotopes known to the IsotopeFactory for the given 99 * element symbol. 100 * 101 * @param symbol An element symbol to search for 102 * @return An array of isotopes that matches the given element symbol 103 */ getIsotopes(String symbol)104 public IIsotope[] getIsotopes(String symbol) { 105 return getIsotopes(Elements.ofString(symbol).number()); 106 } 107 108 /** 109 * Gets a array of all isotopes known to the IsotopeFactory. 110 * 111 * @return An array of all isotopes 112 */ getIsotopes()113 public IIsotope[] getIsotopes() { 114 List<IIsotope> list = new ArrayList<IIsotope>(); 115 for (List<IIsotope> isotopes : this.isotopes) { 116 if (isotopes == null) continue; 117 for (IIsotope isotope : isotopes) { 118 list.add(clone(isotope)); 119 } 120 } 121 return list.toArray(new IIsotope[list.size()]); 122 } 123 124 /** 125 * Gets an array of all isotopes matching the searched exact mass within 126 * a certain difference. 127 * 128 * @param exactMass search mass 129 * @param difference mass the isotope is allowed to differ from the search mass 130 * @return An array of all isotopes 131 */ getIsotopes(double exactMass, double difference)132 public IIsotope[] getIsotopes(double exactMass, double difference) { 133 List<IIsotope> list = new ArrayList<>(); 134 for (List<IIsotope> isotopes : this.isotopes) { 135 if (isotopes == null) continue; 136 for (IIsotope isotope : isotopes) { 137 if (Math.abs(isotope.getExactMass() - exactMass) <= difference) { 138 list.add(clone(isotope)); 139 } 140 } 141 } 142 return list.toArray(new IIsotope[list.size()]); 143 } 144 145 /** 146 * Get isotope based on element symbol and mass number. 147 * 148 * @param symbol the element symbol 149 * @param massNumber the mass number 150 * @return the corresponding isotope 151 */ getIsotope(String symbol, int massNumber)152 public IIsotope getIsotope(String symbol, int massNumber) { 153 int elem = Elements.ofString(symbol).number(); 154 List<IIsotope> isotopes = this.isotopes[elem]; 155 if (isotopes == null) 156 return null; 157 for (IIsotope isotope : isotopes) { 158 if (isotope.getSymbol().equals(symbol) && isotope.getMassNumber() == massNumber) { 159 return clone(isotope); 160 } 161 } 162 return null; 163 } 164 165 /** 166 * Get an isotope based on the element symbol and exact mass. 167 * 168 * @param symbol the element symbol 169 * @param exactMass the mass number 170 * @param tolerance allowed difference from provided exact mass 171 * @return the corresponding isotope 172 */ getIsotope(String symbol, double exactMass, double tolerance)173 public IIsotope getIsotope(String symbol, double exactMass, double tolerance) { 174 IIsotope ret = null; 175 double minDiff = Double.MAX_VALUE; 176 int elem = Elements.ofString(symbol).number(); 177 List<IIsotope> isotopes = this.isotopes[elem]; 178 if (isotopes == null) 179 return null; 180 for (IIsotope isotope : isotopes) { 181 double diff = Math.abs(isotope.getExactMass() - exactMass); 182 if (isotope.getSymbol().equals(symbol) && diff <= tolerance && diff < minDiff) { 183 ret = clone(isotope); 184 minDiff = diff; 185 } 186 } 187 return ret; 188 } 189 190 /** 191 * Returns the most abundant (major) isotope with a given atomic number. 192 * Note that some high mass elements do not have a major isotopes 193 * (0% abundance) and this method will return null for those. 194 * 195 * @param elem The atomicNumber for which an isotope is to be returned 196 * @return The isotope corresponding to the given atomic number 197 * 198 * @see #getMajorIsotope(String symbol) 199 */ getMajorIsotope(int elem)200 public IIsotope getMajorIsotope(int elem) { 201 IIsotope major = null; 202 if (this.majorIsotope[elem] != null) { 203 return clone(this.majorIsotope[elem]); 204 } 205 List<IIsotope> isotopes = this.isotopes[elem]; 206 if (isotopes != null) { 207 for (IIsotope isotope : isotopes) { 208 if (isotope.getNaturalAbundance() <= 0) 209 continue; 210 if (major == null || 211 isotope.getNaturalAbundance() > major.getNaturalAbundance()) { 212 major = isotope; 213 } 214 } 215 if (major != null) 216 this.majorIsotope[elem] = major; 217 else 218 logger.error("Could not find major isotope for: ", elem); 219 } 220 return clone(major); 221 } 222 223 /** 224 * Get the mass of the most abundant (major) isotope, if there is no 225 * major isotopes 0 is returned. 226 * 227 * @param elem the atomic number 228 * @return the major isotope mass 229 */ getMajorIsotopeMass(int elem)230 public double getMajorIsotopeMass(int elem) { 231 if (this.majorIsotope[elem] != null) 232 return this.majorIsotope[elem].getExactMass(); 233 IIsotope major = getMajorIsotope(elem); 234 return major != null ? major.getExactMass() : 0; 235 } 236 237 /** 238 * Get the exact mass of a specified isotope for an atom. 239 * @param atomicNumber atomic number 240 * @param massNumber the mass number 241 * @return the exact mass 242 */ getExactMass(Integer atomicNumber, Integer massNumber)243 public double getExactMass(Integer atomicNumber, Integer massNumber) { 244 if (atomicNumber == null || massNumber == null) 245 return 0; 246 for (IIsotope isotope : this.isotopes[atomicNumber]) { 247 if (isotope.getMassNumber().equals(massNumber)) 248 return isotope.getExactMass(); 249 } 250 return 0; 251 } 252 clone(IIsotope isotope)253 private IIsotope clone(IIsotope isotope) { 254 if (isotope == null) 255 return null; 256 try { 257 return (IIsotope) isotope.clone(); 258 } catch (CloneNotSupportedException ex) { 259 throw new UnsupportedOperationException("Clone not supported"); 260 } 261 } 262 263 /** 264 * Checks whether the given element exists. 265 * 266 * @param elementName The element name to test 267 * @return True is the element exists, false otherwise 268 */ isElement(String elementName)269 public boolean isElement(String elementName) { 270 return (getElement(elementName) != null); 271 } 272 273 /** 274 * Returns the most abundant (major) isotope whose symbol equals element. 275 * 276 *@param symbol the symbol of the element in question 277 *@return The Major Isotope value 278 */ getMajorIsotope(String symbol)279 public IIsotope getMajorIsotope(String symbol) { 280 return getMajorIsotope(Elements.ofString(symbol).number()); 281 } 282 283 /** 284 * Returns an Element with a given element symbol. 285 * 286 *@param symbol The element symbol for the requested element 287 *@return The configured element 288 */ getElement(String symbol)289 public IElement getElement(String symbol) { 290 return getMajorIsotope(symbol); 291 } 292 293 /** 294 * Returns an element according to a given atomic number. 295 * 296 *@param atomicNumber The elements atomic number 297 *@return The Element 298 */ getElement(int atomicNumber)299 public IElement getElement(int atomicNumber) { 300 return getMajorIsotope(atomicNumber); 301 } 302 303 /** 304 * Returns the symbol matching the element with the given atomic number. 305 * 306 * @param atomicNumber The elements atomic number 307 * @return The symbol of the Element 308 */ getElementSymbol(int atomicNumber)309 public String getElementSymbol(int atomicNumber) { 310 IIsotope isotope = getMajorIsotope(atomicNumber); 311 return isotope.getSymbol(); 312 } 313 314 /** 315 * Configures an atom. Finds the correct element type 316 * by looking at the atoms element symbol. If the element symbol is not recognised, it will 317 * throw an {@link IllegalArgumentException}. 318 * 319 * @param atom The atom to be configured 320 * @return The configured atom 321 */ configure(IAtom atom)322 public IAtom configure(IAtom atom) { 323 IIsotope isotope; 324 if (atom.getMassNumber() == null) 325 return atom; 326 else 327 isotope = getIsotope(atom.getSymbol(), atom.getMassNumber()); 328 if (isotope == null) 329 throw new IllegalArgumentException("Cannot configure an unrecognized element/mass: " + atom.getMassNumber() + " " + atom); 330 return configure(atom, isotope); 331 } 332 333 /** 334 * Configures an atom to have all the data of the 335 * given isotope. 336 * 337 *@param atom The atom to be configure 338 *@param isotope The isotope to read the data from 339 *@return The configured atom 340 */ configure(IAtom atom, IIsotope isotope)341 public IAtom configure(IAtom atom, IIsotope isotope) { 342 atom.setMassNumber(isotope.getMassNumber()); 343 atom.setSymbol(isotope.getSymbol()); 344 atom.setExactMass(isotope.getExactMass()); 345 atom.setAtomicNumber(isotope.getAtomicNumber()); 346 atom.setNaturalAbundance(isotope.getNaturalAbundance()); 347 return atom; 348 } 349 350 /** 351 * Configures atoms in an AtomContainer to 352 * carry all the correct data according to their element type. 353 * 354 *@param container The AtomContainer to be configured 355 */ configureAtoms(IAtomContainer container)356 public void configureAtoms(IAtomContainer container) { 357 for (int f = 0; f < container.getAtomCount(); f++) { 358 configure(container.getAtom(f)); 359 } 360 } 361 362 /** 363 * Gets the natural mass of this element, defined as average of masses of 364 * isotopes, weighted by abundance. 365 * 366 * @param atomicNum the element in question 367 * @return The natural mass value 368 */ getNaturalMass(int atomicNum)369 public double getNaturalMass(int atomicNum) { 370 List<IIsotope> isotopes = this.isotopes[atomicNum]; 371 if (isotopes == null) 372 return 0; 373 double summedAbundances = 0; 374 double summedWeightedAbundances = 0; 375 double getNaturalMass = 0; 376 for (IIsotope isotope : isotopes) { 377 summedAbundances += isotope.getNaturalAbundance(); 378 summedWeightedAbundances += isotope.getNaturalAbundance() * isotope.getExactMass(); 379 getNaturalMass = summedWeightedAbundances / summedAbundances; 380 } 381 return getNaturalMass; 382 } 383 384 /** 385 * Gets the natural mass of this element, defined as average of masses of 386 * isotopes, weighted by abundance. 387 * 388 * @param element the element in question 389 * @return The natural mass value 390 */ getNaturalMass(IElement element)391 public double getNaturalMass(IElement element) { 392 return getNaturalMass(element.getAtomicNumber()); 393 } 394 } 395