001package com.hfg.chem;
002
003import java.io.BufferedReader;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.InputStreamReader;
007import java.util.List;
008import java.util.Map;
009
010import com.hfg.chem.format.ChemIOException;
011import com.hfg.exception.ProgrammingException;
012import com.hfg.util.StringUtil;
013import com.hfg.util.collection.OrderedMap;
014import com.hfg.util.io.RuntimeIOException;
015import com.hfg.util.io.StreamUtil;
016
017//------------------------------------------------------------------------------
018/**
019 Valence Model for assigning valence values to Atoms.
020 <p>
021 @author J. Alex Taylor, hairyfatguy.com
022 </p>
023 */
024//------------------------------------------------------------------------------
025// com.hfg XML/HTML Coding Library
026//
027// This library is free software; you can redistribute it and/or
028// modify it under the terms of the GNU Lesser General Public
029// License as published by the Free Software Foundation; either
030// version 2.1 of the License, or (at your option) any later version.
031//
032// This library is distributed in the hope that it will be useful,
033// but WITHOUT ANY WARRANTY; without even the implied warranty of
034// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
035// Lesser General Public License for more details.
036//
037// You should have received a copy of the GNU Lesser General Public
038// License along with this library; if not, write to the Free Software
039// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
040//
041// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
042// jataylor@hairyfatguy.com
043//------------------------------------------------------------------------------
044
045public class ValenceModel
046{
047   public OrderedMap<Element, OrderedMap<Integer, int[]>> mValenceValueMap = new OrderedMap<>();
048
049
050   private static final int[] ZERO_VALUE = new int[] { 0 };
051
052   public static final ValenceModel MDL_Pre2017 = new ValenceModel();
053   public static final ValenceModel MDL_2017 = new ValenceModel();
054   static
055   {
056      MDL_Pre2017.load("rsrc/MDL_ValenceModelPre2017.txt");
057      MDL_2017.load("rsrc/MDL_ValenceModel2017.txt");
058   }
059
060   //##########################################################################
061   // CONSTRUCTORS
062   //##########################################################################
063
064   //---------------------------------------------------------------------------
065   private ValenceModel()
066   {
067
068   }
069
070   //##########################################################################
071   // PUBLIC METHODS
072   //##########################################################################
073
074   //---------------------------------------------------------------------------
075   /**
076    Returns the values for the number of valence electrons for the element with
077    the specified charge. Used when calculating implicit hydrogens for a compound.
078    */
079   public int[] getValences(Element inElement, int inCharge)
080   {
081      int[] values = null;
082
083      Map<Integer, int[]> elementValences = mValenceValueMap.get(inElement);
084      if (elementValences != null)
085      {
086         values = elementValences.get(inCharge);
087      }
088
089      return values != null ? values : ZERO_VALUE;
090   }
091
092   //--------------------------------------------------------------------------
093   public int calculateImplicitHCount(Atom inAtom)
094   {
095      Integer implicitHCount = null;
096
097      // Don't calculate implicit hydrogens if an explicit value has been specified
098      if (null == inAtom.getHCount())
099      {
100         int bondOrder = 0;
101         List<CovalentBond> bonds = inAtom.getBonds();
102         if (bonds != null)
103         {
104            for (CovalentBond bond : bonds)
105            {
106               // TODO: Should we bail if bonds to hydrogens are present?
107               bondOrder += bond.getBondOrder();
108            }
109         }
110
111         // TODO: Should the hCount hydrogens have already been added with bonds?
112
113         Integer charge = inAtom.getCharge();
114         if (charge != null
115             && charge < 0)
116         {
117            bondOrder += Math.abs(charge);
118         }
119
120         if (inAtom.isAromatic())
121         {
122            bondOrder++;
123         }
124
125         int[] defaultValences = getValences(inAtom.getElement(), charge != null ? charge : 0);
126         if (defaultValences != null)
127         {
128            // Find the next highest valence
129            for (int i = 0; i < defaultValences.length; i++)
130            {
131               if (defaultValences[i] >= bondOrder)
132               {
133                  implicitHCount = defaultValences[i] - bondOrder;
134                  break;
135               }
136            }
137         }
138
139         inAtom.setImplicitHCount(implicitHCount);
140      }
141
142      return implicitHCount != null ? implicitHCount : 0;
143   }
144
145   //##########################################################################
146   // PRIVATE METHODS
147   //##########################################################################
148
149   //---------------------------------------------------------------------------
150   private void load(String inRsrc)
151   {
152      InputStream stream = getClass().getResourceAsStream(inRsrc);
153      if (null == stream)
154      {
155         throw new ProgrammingException("The rsrc " + StringUtil.singleQuote(inRsrc) + " couldn't be found!?");
156      }
157
158      BufferedReader reader = null;
159      try
160      {
161         reader = new BufferedReader(new InputStreamReader(stream));
162
163         String line;
164         int lineNum = 0;
165         while ((line = reader.readLine()) != null)
166         {
167            lineNum++;
168
169            if (!StringUtil.isSet(line) // Skip blank lines
170                || 1 == lineNum // Skip the header line
171                || line.startsWith("#")) // Skip comment lines
172            {
173               continue;
174            }
175
176            String[] fields = line.split(";", -1); // -1 as the limit keeps it from discarding trailing empty fields
177            if (fields.length != 11)
178            {
179               throw new ChemIOException(String.format("Unexpected number of fields (%d) on line %d of %s!", fields.length, lineNum, inRsrc));
180            }
181
182            Element element = Element.valueOf(fields[1].trim());
183            if (null == element)
184            {
185               throw new ChemIOException(String.format("Unrecognized element (%s) on line %d of %s!", fields[1], lineNum, inRsrc));
186            }
187
188            OrderedMap<Integer, int[]> values = new OrderedMap<>(7);
189            for (int i = 2; i < 11; i++)
190            {
191               if (StringUtil.isSet(fields[i]))
192               {
193                  String[] valenceValueStrings = fields[i].trim().split(",");
194                  if (valenceValueStrings.length > 1
195                      || ! valenceValueStrings[0].equals("0"))
196                  {
197                     int charge = i - 5;
198                     int[] valenceValues = new int[valenceValueStrings.length];
199                     for (int j = 0; j < valenceValueStrings.length; j++)
200                     {
201                        valenceValues[j] = Integer.parseInt(valenceValueStrings[j]);
202                     }
203
204                     values.put(charge, valenceValues);
205                  }
206               }
207            }
208
209            mValenceValueMap.put(element, values);
210         }
211      }
212      catch (IOException e)
213      {
214         throw new RuntimeIOException("Problem parsing " + inRsrc + "!");
215      }
216      finally
217      {
218         StreamUtil.close(reader);
219      }
220   }
221}