/* $RCSfile$
* $Author$
* $Date$
* $Revision$
*
* Copyright (C) 2003-2007 The Chemistry Development Kit (CDK) project
*
* 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.validate;
import java.util.Iterator;
import org.openscience.cdk.Atom;
import org.openscience.cdk.AtomContainer;
import org.openscience.cdk.Bond;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.Molecule;
import org.openscience.cdk.PseudoAtom;
import org.openscience.cdk.Reaction;
import org.openscience.cdk.config.AtomTypeFactory;
import org.openscience.cdk.config.IsotopeFactory;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomType;
import org.openscience.cdk.interfaces.IElement;
import org.openscience.cdk.interfaces.IIsotope;
import org.openscience.cdk.interfaces.IMoleculeSet;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
import org.openscience.cdk.tools.manipulator.BondManipulator;
/**
* Validator which tests a number of basic chemical semantics.
*
* @author Egon Willighagen
* @cdk.githash
* @cdk.created 2003-08-22
*/
public class BasicValidator extends AbstractValidator {
private static ILoggingTool logger =
LoggingToolFactory.createLoggingTool(BasicValidator.class);
public BasicValidator() {
}
public ValidationReport validateAtom(Atom subject) {
ValidationReport report = new ValidationReport();
report.addReport(validateCharge(subject));
report.addReport(validateHydrogenCount(subject));
report.addReport(validatePseudoAtom(subject));
return report;
}
public ValidationReport validateBond(Bond subject) {
ValidationReport report = new ValidationReport();
report.addReport(validateStereoChemistry(subject));
report.addReport(validateMaxBondOrder(subject));
return report;
}
public ValidationReport validateIsotope(IIsotope subject) {
return validateIsotopeExistence(subject);
}
public ValidationReport validateMolecule(Molecule subject) {
ValidationReport report = new ValidationReport();
ValidationTest emptyMolecule = new ValidationTest(subject,
"Molecule does not contain any atom"
);
if (subject.getAtomCount() == 0) {
report.addError(emptyMolecule);
} else {
report.addOK(emptyMolecule);
ValidationTest massCalcProblem = new ValidationTest(subject,
"Molecule contains PseudoAtom's. Won't be able to calculate some properties, like molecular mass."
);
boolean foundMassCalcProblem = false;
for (int i=0; i<subject.getAtomCount(); i++) {
if (subject.getAtom(i) instanceof PseudoAtom) {
foundMassCalcProblem = true;
} else {
report.addReport(validateBondOrderSum(subject.getAtom(i), subject));
}
}
if (foundMassCalcProblem) {
report.addWarning(massCalcProblem);
} else {
report.addOK(massCalcProblem);
}
}
return report;
}
public ValidationReport validateReaction(Reaction subject) {
ValidationReport report = new ValidationReport();
AtomContainer container1 = new AtomContainer();
IMoleculeSet reactants = subject.getReactants();
for (int i=0; i<reactants.getAtomContainerCount(); i++) {
container1.add(reactants.getMolecule(i));
}
AtomContainer container2 = new AtomContainer();
IMoleculeSet products = subject.getProducts();
for (int i=0; i<products.getAtomContainerCount(); i++) {
container2.add(products.getMolecule(i));
}
report.addReport(validateAtomCountConservation(subject, container1, container2));
report.addReport(validateChargeConservation(subject, container1, container2));
return report;
}
// the Atom tests
private ValidationReport validateCharge(Atom atom) {
ValidationReport report = new ValidationReport();
ValidationTest tooCharged = new ValidationTest(atom, "Atom has an unlikely large positive or negative charge");
if (atom.getSymbol().equals("O") || atom.getSymbol().equals("N") ||
atom.getSymbol().equals("C") || atom.getSymbol().equals("H")) {
if (atom.getFormalCharge() == 0) {
report.addOK(tooCharged);
} else {
tooCharged.setDetails("Atom " + atom.getSymbol() + " has charge " + atom.getFormalCharge() );
if (atom.getFormalCharge() < -3) {
report.addError(tooCharged);
} else if (atom.getFormalCharge() < -1) {
report.addWarning(tooCharged);
} else if (atom.getFormalCharge() > 3) {
report.addError(tooCharged);
} else if (atom.getFormalCharge() > 1) {
report.addWarning(tooCharged);
}
}
} else {
if (atom.getFormalCharge() == 0) {
report.addOK(tooCharged);
} else {
tooCharged.setDetails("Atom " + atom.getSymbol() + " has charge " + atom.getFormalCharge() );
if (atom.getFormalCharge() < -4) {
report.addError(tooCharged);
} else if (atom.getFormalCharge() < -3) {
report.addWarning(tooCharged);
} else if (atom.getFormalCharge() > 4) {
report.addError(tooCharged);
} else if (atom.getFormalCharge() > 3) {
report.addWarning(tooCharged);
}
}
}
return report;
}
private ValidationReport validateHydrogenCount(Atom atom) {
ValidationReport report = new ValidationReport();
ValidationTest negativeHydrogenCount = new ValidationTest(atom,
"An Atom cannot have a negative number of hydrogens attached."
);
if (atom.getHydrogenCount() < 0 ) {
negativeHydrogenCount.setDetails(
"Atom has " + atom.getHydrogenCount() + " hydrogens."
);
report.addError(negativeHydrogenCount);
} else {
report.addOK(negativeHydrogenCount);
}
return report;
}
private ValidationReport validatePseudoAtom(Atom atom) {
ValidationReport report = new ValidationReport();
ValidationTest isElementOrPseudo = new ValidationTest(atom,
"Non-element atom must be of class PseudoAtom."
);
if (atom instanceof PseudoAtom) {
// that's fine
report.addOK(isElementOrPseudo);
} else {
// check whether atom is really an element
try {
IsotopeFactory isotopeFactory = IsotopeFactory.getInstance(atom.getBuilder());
IElement element = isotopeFactory.getElement(atom.getSymbol());
if (element == null) {
isElementOrPseudo.setDetails(
"Element " + atom.getSymbol() + " does not exist."
);
report.addError(isElementOrPseudo);
} else {
report.addOK(isElementOrPseudo);
}
} catch (Exception exception) {
// well... don't throw an error then.
isElementOrPseudo.setDetails(exception.toString());
report.addCDKError(isElementOrPseudo);
}
}
return report;
}
// the Bond tests
private ValidationReport validateStereoChemistry(Bond bond) {
ValidationReport report = new ValidationReport();
ValidationTest bondStereo = new ValidationTest(bond,
"Defining stereochemistry on bonds is not safe.",
"Use atom based stereochemistry."
);
if (bond.getStereo() != IBond.Stereo.NONE) {
report.addWarning(bondStereo);
} else {
report.addOK(bondStereo);
}
return report;
}
private ValidationReport validateMaxBondOrder(Bond bond) {
ValidationReport report = new ValidationReport();
ValidationTest maxBO = new ValidationTest(bond,
"Bond order exceeds the maximum for one of its atoms."
);
try {
AtomTypeFactory structgenATF = AtomTypeFactory.getInstance(
"org/openscience/cdk/dict/data/cdk-atom-types.owl",
bond.getBuilder()
);
for (int i=0; i<bond.getAtomCount(); i++) {
IAtom atom = bond.getAtom(i);
if (atom instanceof PseudoAtom) {
// ok, all is fine; we don't know the properties of pseudo atoms
break;
}
IAtomType[] atomTypes = structgenATF.getAtomTypes(atom.getSymbol());
IAtomType failedOn = null;
boolean foundMatchingAtomType = false;
for (int j=0; j<atomTypes.length; j++) {
if (!BondManipulator.isHigherOrder(bond.getOrder(), atomTypes[j].getMaxBondOrder())) {
foundMatchingAtomType = true;
} else {
failedOn = atomTypes[j];
}
}
if (foundMatchingAtomType) {
report.addOK(maxBO);
} else {
if (failedOn != null) {
maxBO.setDetails(
"Bond order exceeds the one allowed for atom " +
atom.getSymbol() + " for which the maximum bond order is " +
failedOn.getMaxBondOrder()
);
}
report.addError(maxBO);
}
}
} catch (Exception exception) {
logger.error("Error while performing atom bos validation");
logger.debug(exception);
maxBO.setDetails("Error while performing atom bos validation: " +
exception.toString());
report.addCDKError(maxBO);
}
return report;
}
// the Isotope tests
public ValidationReport validateIsotopeExistence(IIsotope isotope) {
ValidationReport report = new ValidationReport();
ValidationTest isotopeExists = new ValidationTest(isotope,
"Isotope with this mass number is not known for this element."
);
try {
IsotopeFactory isotopeFac = IsotopeFactory.getInstance(isotope.getBuilder());
IIsotope[] isotopes = isotopeFac.getIsotopes(isotope.getSymbol());
if (isotope.getMassNumber() != 0) {
boolean foundKnownIsotope = false;
for (int i=0; i<isotopes.length; i++) {
if (isotopes[i].getMassNumber() == isotope.getMassNumber()) {
foundKnownIsotope = true;
}
}
if (!foundKnownIsotope) {
report.addError(isotopeExists);
} else {
report.addOK(isotopeExists);
}
} else {
// isotopic number is not set
report.addOK(isotopeExists);
}
} catch (Exception exception) {
// too bad...
}
return report;
}
// the Molecule tests
private ValidationReport validateBondOrderSum(IAtom atom, Molecule molecule) {
ValidationReport report = new ValidationReport();
ValidationTest checkBondSum = new ValidationTest(atom,
"The atom's total bond order is too high."
);
try {
AtomTypeFactory structgenATF = AtomTypeFactory.getInstance(
"org/openscience/cdk/dict/data/cdk-atom-types.owl",
atom.getBuilder()
);
int bos = (int)molecule.getBondOrderSum(atom);
org.openscience.cdk.interfaces.IAtomType[] atomTypes = structgenATF.getAtomTypes(atom.getSymbol());
if (atomTypes.length == 0) {
checkBondSum.setDetails(
"Cannot validate bond order sum for atom not in valency atom type list: " +
atom.getSymbol()
);
report.addWarning(checkBondSum);
} else {
IAtomType failedOn = null;
boolean foundMatchingAtomType = false;
for (int j=0; j<atomTypes.length; j++) {
IAtomType type = atomTypes[j];
if (atom.getFormalCharge() == type.getFormalCharge()) {
foundMatchingAtomType = true;
if (bos == type.getBondOrderSum()) {
// skip this atom type
} else {
failedOn = atomTypes[j];
}
}
}
if (foundMatchingAtomType) {
report.addOK(checkBondSum);
} else {
if (failedOn != null) {
checkBondSum.setDetails(
"Bond order exceeds the one allowed for atom " +
atom.getSymbol() + " for which the total bond order is " +
failedOn.getBondOrderSum()
);
}
report.addError(checkBondSum);
}
}
} catch (Exception exception) {
logger.error("Error while performing atom bos validation: ", exception.getMessage());
logger.debug(exception);
}
return report;
}
private ValidationReport validateAtomCountConservation(Reaction reaction,
AtomContainer reactants,
AtomContainer products) {
ValidationReport report = new ValidationReport();
ValidationTest atomCount = new ValidationTest(reaction,
"Atom count mismatch for reaction: the product side has a different atom count than the reactant side."
);
if (reactants.getAtomCount() != products.getAtomCount()) {
report.addError(atomCount);
} else {
report.addOK(atomCount);
}
return report;
}
private ValidationReport validateChargeConservation(Reaction reaction,
AtomContainer reactants,
AtomContainer products) {
ValidationReport report = new ValidationReport();
ValidationTest chargeConservation = new ValidationTest(reaction,
"Total formal charge is not preserved during the reaction"
);
Iterator<IAtom> atoms1 = reactants.atoms().iterator();
int totalCharge1 = 0;
while (atoms1.hasNext()) {
totalCharge1 =+ ((IAtom)atoms1.next()).getFormalCharge();
}
Iterator<IAtom> atoms2 = products.atoms().iterator();
int totalCharge2 = 0;
while (atoms2.hasNext()) {
totalCharge2 =+ ((IAtom)atoms2.next()).getFormalCharge();
}
if (totalCharge1 != totalCharge2) {
report.addError(chargeConservation);
} else {
report.addOK(chargeConservation);
}
return report;
}
}