/* $RCSfile$
* $Author: egonw $
* $Date: 2008-05-11 08:06:41 +0200 (Sun, 11 May 2008) $
* $Revision: 10958 $
*
* Copyright (C) 2007 Miguel Rojasch <miguelrojasch@users.sf.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk.formula.rules;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IIsotope;
import org.openscience.cdk.interfaces.IMolecularFormula;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
import org.openscience.cdk.tools.manipulator.MolecularFormulaManipulator;
/**
* <p>Ring Double Bond Equivalents (RDBE) or
* Double Bond Equivalents (DBE) are calculated from valence values of
* elements contained in a formula and should tell the number of bonds - or rings.
* Since this formula will fail for MFs with higher valence states such as
* N(V), P(V), S(IV) or S(VI), this method will focus on the lowest valence state for these elements.</p>
* <p>The equation used is: D = 1 + [0.5 SUM_i(N_i(V_I-2))]</p>
* <p>where D is the unsaturation, i is the total number of different elements in the composition, N_i the number
* of atoms of element i, and Vi is the common valence of the atom i.</p>
* <p>This rule uses these parameters:
* <table border="1">
* <tr>
* <td>Name</td>
* <td>Default</td>
* <td>Description</td>
* </tr>
* <tr>
* <td>charge</td>
* <td>0.0</td>
* <td>The RDBE rule of MolecularFormula</td>
* </tr>
* </table>
*
* @cdk.module formula
* @author miguelrojasch
* @cdk.created 2008-06-11
*/
public class RDBERule implements IRule{
private static Map<String,int[]> oxidationStateTable = null;
private static ILoggingTool logger =
LoggingToolFactory.createLoggingTool(RDBERule.class);
private double min = -0.5;
private double max = 30;
/**
* Constructor for the RDBE object.
*/
public RDBERule() {
createTable();
}
/**
* Sets the parameters attribute of the RDBE object.
*
* @param params The new parameters value
* @throws CDKException Description of the Exception
*
* @see #getParameters
*/
public void setParameters(Object[] params) throws CDKException {
if (params.length != 2)
throw new CDKException("RDBERule expects two parameters");
if(!(params[0] instanceof Double))
throw new CDKException("The 1 parameter must be of type Double");
if(!(params[1] instanceof Double))
throw new CDKException("The 2 parameter must be of type Double");
min = (Double)params[0];
max = (Double)params[1];
}
/**
* Gets the parameters attribute of the RDBRule object.
*
* @return The parameters value
* @see #setParameters
*/
public Object[] getParameters() {
// return the parameters as used for the rule validation
Object[] params = new Object[2];
params[0] = min;
params[1] = max;
return params;
}
/**
* Validate the RDBRule of this IMolecularFormula.
*
* @param formula Parameter is the IMolecularFormula
* @return A double value meaning 1.0 True, 0.0 False
*/
public double validate(IMolecularFormula formula) throws CDKException {
logger.info("Start validation of ",formula);
List<Double> RDBEList = getRDBEValue(formula);
for(Iterator<Double> it = RDBEList.iterator(); it.hasNext();){
double RDBE = it.next();
if(min <= RDBE && RDBE <= 30)
if(validate(formula, RDBE))
return 1.0;
}
return 0.0;
}
/**
* Validate the ion state. It takes into account that neutral, nonradical compounds
* always have an even-numbered pair-wiser arrangement of binding electrons signilizaded
* by an integer DBE value. Charged compounds due to soft ionzation techniques
* will give an odd number of binding electrons and a fractional DBE (X.05).
*
* @param formula Parameter is the IMolecularFormula
* @param value The RDBE value
* @return True, if corresponds with
*/
public boolean validate(IMolecularFormula formula, double value) throws CDKException {
double charge = 0.0;
if(formula.getCharge() != CDKConstants.UNSET)
charge = formula.getCharge();
long iPart = (long) value;
double fPart = value - iPart;
if(fPart == 0.0 && charge == 0)
return true;
if(fPart != 0.0 && charge != 0)
return true;
else
return false;
}
/**
* Method to extract the Ring Double Bond Equivalents (RDB) value. It test all possible
* oxidation states.
*
* @param formula The IMolecularFormula object
* @return The RDBE value
* @see #createTable()
*/
public List<Double> getRDBEValue(IMolecularFormula formula) {
List<Double> RDBEList = new ArrayList<Double>();
// The number of combinations with repetition
// (v+n-1)!/[n!(v-1)!]
int nE = 0; // number of elements to change
List<Integer> nV = new ArrayList<Integer>(); // number of valence changing
for(Iterator<IIsotope> it = formula.isotopes().iterator(); it.hasNext();){
IIsotope isotope = it.next();
int[] valence = getOxidationState(formula.getBuilder().newAtom(isotope.getSymbol()));
if(valence.length != 1){
for(int i = 0; i < valence.length; i++){
nV.add(valence[i]);
}
nE += MolecularFormulaManipulator.getElementCount(formula, formula.getBuilder().newElement(isotope.getSymbol()));
}
}
double RDBE = 0;
if(nE == 0){
for(Iterator<IIsotope> it = formula.isotopes().iterator(); it.hasNext();){
IIsotope isotope = it.next();
int[] valence = getOxidationState(formula.getBuilder().newAtom(isotope.getSymbol()));
double value = (valence[0]-2)*formula.getIsotopeCount(isotope)/2.0;
RDBE += value;
}
RDBE += 1;
RDBEList.add(RDBE);
}else{
double RDBE_1 = 0;
for(Iterator<IIsotope> it = formula.isotopes().iterator(); it.hasNext();){
IIsotope isotope = it.next();
int[] valence = getOxidationState(formula.getBuilder().newAtom(isotope.getSymbol()));
double value = (valence[0]-2)*formula.getIsotopeCount(isotope)*0.5;
RDBE_1 += value;
}
String[] valences = new String[nV.size()];
for(int i = 0 ; i < valences.length; i++)
valences[i] = Integer.toString(nV.get(i));
Combinations c = new Combinations(valences, nE);
while (c.hasMoreElements()) {
double RDBE_int = 0.0;
Object[] combo = (Object[])c.nextElement();
for (int i = 0; i < combo.length; i++) {
int value = (Integer.parseInt((String)combo[i])-2)/2;
RDBE_int += value;
}
RDBE = 1 + RDBE_1 + RDBE_int;
RDBEList.add(RDBE);
}
}
return RDBEList;
}
/**
* Get the common oxidation state given a atom.
*
* @param newAtom The IAtom
* @return The oxidation state value
*/
private int[] getOxidationState(IAtom newAtom) {
return oxidationStateTable.get(newAtom.getSymbol());
}
/**
* Create the table with the common oxidation states
*/
private void createTable() {
if (oxidationStateTable == null) {
oxidationStateTable = new HashMap<String,int[]>();
oxidationStateTable.put("H", new int[]{1});
// oxidationStateTable.put("Li", 1);
// oxidationStateTable.put("Be", 2);
oxidationStateTable.put("B", new int[]{3});
oxidationStateTable.put("C", new int[]{4});
oxidationStateTable.put("N", new int[]{3});
oxidationStateTable.put("O", new int[]{2});
oxidationStateTable.put("F", new int[]{1});
oxidationStateTable.put("Na", new int[]{1});
oxidationStateTable.put("Mg", new int[]{2});
oxidationStateTable.put("Al", new int[]{3});
oxidationStateTable.put("Si", new int[]{4});
oxidationStateTable.put("P", new int[]{3,5});
oxidationStateTable.put("S", new int[]{2,4,6});
oxidationStateTable.put("Cl", new int[]{1});
// oxidationStateTable.put("K", 1);
// oxidationStateTable.put("Ca", 2);
// oxidationStateTable.put("Ga", 3);
// oxidationStateTable.put("Ge", 4);
// oxidationStateTable.put("As", 5);
// oxidationStateTable.put("Se", 6);
// oxidationStateTable.put("Br", 7);
// oxidationStateTable.put("Rb", 1);
// oxidationStateTable.put("Sr", 2);
// oxidationStateTable.put("In", 3);
// oxidationStateTable.put("Sn", 4);
// oxidationStateTable.put("Sb", 5);
// oxidationStateTable.put("Te", 6);
oxidationStateTable.put("I", new int[]{1});
// oxidationStateTable.put("Cs", 1);
// oxidationStateTable.put("Ba", 2);
// oxidationStateTable.put("Tl", 3);
// oxidationStateTable.put("Pb", 4);
// oxidationStateTable.put("Bi", 5);
// oxidationStateTable.put("Po", 6);
// oxidationStateTable.put("At", 7);
// oxidationStateTable.put("Fr", 1);
// oxidationStateTable.put("Ra", 2);
// oxidationStateTable.put("Cu", 2);
// oxidationStateTable.put("Mn", 2);
// oxidationStateTable.put("Co", 2);
}
}
public class Combinations{
private Object[] inArray;
private int n, m;
private int[] index;
private boolean hasMore = true;
/**
* Create a Combination to enumerate through all subsets of the
* supplied Object array, selecting m at a time.
*
* @param inArray the group to choose from
* @param m int the number to select in each choice
*/
public Combinations(Object[] inArray, int m){
this.inArray = inArray;
this.n = inArray.length;
this.m = m;
/**
* index is an array of ints that keep track of the next combination to return.
* For example, an index on 5 things taken 3 at a time might contain {0 3 4}.
* This index will be followed by {1 2 3}. Initially, the index is {0 ... m - 1}.
*/
index = new int[m];
for (int i = 0; i < m; i++)
index[0] = 0;
}
/**
* @return true, unless we have already returned the last combination.
*/
public boolean hasMoreElements()
{
return hasMore;
}
/**
* Move the index forward a notch. The algorithm finds the rightmost
* index element that can be incremented, increments it, and then
* changes the elements to the right to each be 1 plus the element on their left.
* <p>
* For example, if an index of 5 things taken 3 at a time is at {0 3 4}, only the 0 can
* be incremented without running out of room. The next index is {1, 1+1, 1+2) or
* {1, 2, 3}. This will be followed by {1, 2, 4}, {1, 3, 4}, and {2, 3, 4}.
* <p>
* The algorithm is from Applied Combinatorics, by Alan Tucker.
*
*/
private void moveIndex(){
int i = rightmostIndexBelowMax();
if (i >= 0){
index[i] = index[i] + 1;
for (int j = i + 1; j < m; j++)
index[j] = index[j-1];
}else
hasMore = false;
}
/**
* @return java.lang.Object, the next combination from the supplied Object array.
* <p>
* Actually, an array of Objects is returned. The declaration must say just Object,
* because the Combinations class implements Enumeration, which declares that the
* nextElement() returns a plain Object. Users must cast the returned object to (Object[]).
*/
public Object nextElement()
{
if (!hasMore)
return null;
Object[] out = new Object[m];
for (int i = 0; i < m; i++){
out[i] = inArray[index[i]];
}
moveIndex();
return out;
}
/**
* @return int, the index which can be bumped up.
*/
private int rightmostIndexBelowMax(){
for (int i = m-1; i >= 0; i--){
int s = n -1;
if (index[i] != s)
return i;
}
return -1;
}
}
}