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