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}