MayaChemTools

   1 package MDLMolFileIO;
   2 #
   3 # $RCSfile: MDLMolFileIO.pm,v $
   4 # $Date: 2008/04/22 02:54:50 $
   5 # $Revision: 1.13 $
   6 #
   7 # Author: Manish Sud <msud@san.rr.com>
   8 #
   9 # Copyright (C) 2004-2008 Manish Sud. All rights reserved.
  10 #
  11 # This file is part of MayaChemTools.
  12 #
  13 # MayaChemTools is free software; you can redistribute it and/or modify it under
  14 # the terms of the GNU Lesser General Public License as published by the Free
  15 # Software Foundation; either version 3 of the License, or (at your option) any
  16 # later version.
  17 #
  18 # MayaChemTools is distributed in the hope that it will be useful, but without
  19 # any warranty; without even the implied warranty of merchantability of fitness
  20 # for a particular purpose.  See the GNU Lesser General Public License for more
  21 # details.
  22 #
  23 # You should have received a copy of the GNU Lesser General Public License
  24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
  25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
  26 # Boston, MA, 02111-1307, USA.
  27 #
  28 use 5.006;
  29 use strict;
  30 use Carp;
  31 use Exporter;
  32 use Scalar::Util ();
  33 use TextUtil ();
  34 use FileUtil ();
  35 use SDFileUtil ();
  36 use FileIO::FileIO;
  37 use Molecule;
  38 
  39 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  40 
  41 $VERSION = '1.00';
  42 @ISA = qw(FileIO Exporter);
  43 @EXPORT = qw();
  44 @EXPORT_OK = qw(IsMDLMolFile);
  45 
  46 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
  47 
  48 # Setup class variables...
  49 my($ClassName);
  50 _InitializeClass();
  51 
  52 # Class constructor...
  53 sub new {
  54   my($Class, %NamesAndValues) = @_;
  55 
  56   # Initialize object...
  57   my $This = $Class->SUPER::new();
  58   bless $This, ref($Class) || $Class;
  59   $This->_InitializeMDLMolFileIO();
  60 
  61   $This->_InitializeMDLMolFileIOProperties(%NamesAndValues);
  62 
  63   return $This;
  64 }
  65 
  66 # Initialize any local object data...
  67 #
  68 sub _InitializeMDLMolFileIO {
  69   my($This) = @_;
  70 
  71   # Nothing to do: Base class FileIO handles default class variables...
  72 
  73   return $This;
  74 }
  75 
  76 # Initialize class ...
  77 sub _InitializeClass {
  78   #Class name...
  79   $ClassName = __PACKAGE__;
  80 
  81 }
  82 
  83 # Initialize object values...
  84 sub _InitializeMDLMolFileIOProperties {
  85   my($This, %NamesAndValues) = @_;
  86 
  87   # All other property names and values along with all Set/Get<PropertyName> methods
  88   # are implemented on-demand using ObjectProperty class.
  89 
  90   my($Name, $Value, $MethodName);
  91   while (($Name, $Value) = each  %NamesAndValues) {
  92     $MethodName = "Set${Name}";
  93     $This->$MethodName($Value);
  94   }
  95 
  96   if (!exists $NamesAndValues{Name}) {
  97     croak "Error: ${ClassName}->New: Object can't be instantiated without specifying file name...";
  98   }
  99 
 100   # Make sure it's a MDLMol file...
 101   $Name = $NamesAndValues{Name};
 102   if (!$This->IsMDLMolFile($Name)) {
 103     croak "Error: ${ClassName}->New: Object can't be instantiated: File, $Name, doesn't appear to be MDLMol format...";
 104   }
 105 
 106   return $This;
 107 }
 108 
 109 # Is it a MDLMol file?
 110 sub IsMDLMolFile ($;$) {
 111   my($FirstParameter, $SecondParameter) = @_;
 112   my($This, $FileName, $Status);
 113 
 114   if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) {
 115     ($This, $FileName) = ($FirstParameter, $SecondParameter);
 116   }
 117   else {
 118     $FileName = $FirstParameter;
 119   }
 120 
 121   # Check file extension...
 122   $Status = FileUtil::CheckFileType($FileName, "mol");
 123 
 124   return $Status;
 125 }
 126 
 127 # Read molecule from file and return molecule objest...
 128 sub ReadMolecule {
 129   my($This) = @_;
 130   my($FileHandle);
 131 
 132   $FileHandle = $This->GetFileHandle();
 133   return $This->ParseMoleculeString(SDFileUtil::ReadCmpdString($FileHandle));
 134 }
 135 
 136 # Write compound data using Molecule object...
 137 sub WriteMolecule {
 138   my($This, $Molecule) = @_;
 139 
 140   if (!(defined($Molecule) && $Molecule->IsMolecule())) {
 141     carp "Warning: ${ClassName}->WriteMolecule: No data written: Molecule object is not specified...";
 142     return $This;
 143   }
 144   my($FileHandle);
 145   $FileHandle = $This->GetFileHandle();
 146 
 147   print $FileHandle $This->GenerateMoleculeString($Molecule) . "\n";
 148 
 149   return $This;
 150 }
 151 
 152 # Retrieve molecule string...
 153 sub ReadMoleculeString {
 154   my($This) = @_;
 155   my($FileHandle);
 156 
 157   $FileHandle = $This->GetFileHandle();
 158   return SDFileUtil::ReadCmpdString($FileHandle);
 159 }
 160 
 161 # Parse molecule string and return molecule object. ParseMoleculeString supports two invocation methods: class
 162 # method or a package function.
 163 #
 164 sub ParseMoleculeString {
 165   my($FirstParameter, $SecondParameter) = @_;
 166   my($This, $MoleculeString);
 167 
 168   if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) {
 169     ($This, $MoleculeString) = ($FirstParameter, $SecondParameter);
 170   }
 171   else {
 172     $MoleculeString = $FirstParameter;
 173     $This = undef;
 174   }
 175   if (!$MoleculeString) {
 176     return undef;
 177   }
 178   my($LineIndex, @MoleculeLines);
 179   @MoleculeLines = split /\n/, $MoleculeString;
 180 
 181   # Create molecule object and set molecule level native and MDL properties...
 182   #
 183   my($Molecule);
 184   $Molecule = new Molecule();
 185 
 186   # Process headers data...
 187   $LineIndex = 0;
 188   my($MoleculeName) = SDFileUtil::ParseCmpdMolNameLine($MoleculeLines[$LineIndex]);
 189   $MoleculeName = TextUtil::RemoveTrailingWhiteSpaces($MoleculeName);
 190   $Molecule->SetName($MoleculeName);
 191 
 192   $LineIndex++;
 193   my($UserInitial, $ProgramName, $Date, $Code, $ScalingFactor1, $ScalingFactor2, $Energy, $RegistryNum) = SDFileUtil::ParseCmpdMiscInfoLine($MoleculeLines[$LineIndex]);
 194   $Molecule->SetProperties('MDLUserInitial' => $UserInitial, 'MDLProgramName' => $ProgramName, 'MDLDate' => $Date, 'MDLCode' => $Code, 'MDLScalingFactor1' => $ScalingFactor1, 'MDLScalingFactor2' => $ScalingFactor2, 'MDLEnergy' => $Energy, 'MDLRegistryNum' => $RegistryNum);
 195 
 196   $LineIndex++;
 197   my($Comments) = SDFileUtil::ParseCmpdCommentsLine($MoleculeLines[$LineIndex]);
 198   $Molecule->SetProperties('MDLComments' => $Comments);
 199 
 200   $LineIndex++;
 201   my($AtomCount, $BondCount, $ChiralFlag, $PropertyCount, $Version) = SDFileUtil::ParseCmpdCountsLine($MoleculeLines[$LineIndex]);
 202 
 203   $Molecule->SetProperties('MDLChiralFlag' => $ChiralFlag, 'MDLPropertyCount' => $PropertyCount, 'MDLVersion' => $Version);
 204 
 205   # Process atom data...
 206   my($FirstAtomLineIndex, $LastAtomLineIndex, $AtomNum, $AtomX, $AtomY, $AtomZ, $AtomSymbol, $MassDifference, $Charge, $StereoParity, $Atom, %AtomNumToAtomMap);
 207 
 208   $AtomNum = 0;
 209   %AtomNumToAtomMap = ();
 210   $FirstAtomLineIndex = 4; $LastAtomLineIndex = $FirstAtomLineIndex + $AtomCount - 1;
 211 
 212   for ($LineIndex = $FirstAtomLineIndex; $LineIndex <= $LastAtomLineIndex; $LineIndex++) {
 213     $AtomNum++;
 214     ($AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity) = SDFileUtil::ParseCmpdAtomLine($MoleculeLines[$LineIndex]);
 215 
 216     $Atom = new Atom('AtomSymbol' => $AtomSymbol, 'XYZ' => [$AtomX, $AtomY, $AtomZ]);
 217 
 218     if ($MassDifference && $MassDifference != 0) {
 219       _ProcessMassDifference($Atom, $MassDifference);
 220     }
 221     if ($Charge && $Charge != 0) {
 222       _ProcessCharge($Atom, $Charge);
 223     }
 224     if ($StereoParity && $StereoParity != 0) {
 225       _ProcessStereoParity($Atom, $StereoParity);
 226     }
 227 
 228     $AtomNumToAtomMap{$AtomNum} = $Atom;
 229     $Molecule->AddAtom($Atom);
 230   }
 231 
 232   # Process bond data...
 233   my($FirstBondLineIndex, $LastBondLineIndex, $FirstAtomNum, $SecondAtomNum, $BondType, $BondStereo, $BondOrder, $Bond, $Atom1, $Atom2);
 234 
 235   $FirstBondLineIndex = $FirstAtomLineIndex + $AtomCount;
 236   $LastBondLineIndex = $FirstAtomLineIndex + $AtomCount + $BondCount - 1;
 237 
 238   for ($LineIndex = $FirstBondLineIndex; $LineIndex <= $LastBondLineIndex; $LineIndex++) {
 239     ($FirstAtomNum, $SecondAtomNum, $BondType, $BondStereo) = SDFileUtil::ParseCmpdBondLine($MoleculeLines[$LineIndex]);
 240 
 241     $Atom1 = $AtomNumToAtomMap{$FirstAtomNum};
 242     $Atom2 = $AtomNumToAtomMap{$SecondAtomNum};
 243 
 244     $BondOrder = SDFileUtil::MDLBondTypeToInternalBondOrder($BondType);
 245     $Bond = new Bond('Atoms' => [$Atom1, $Atom2], 'BondOrder' => $BondOrder);
 246 
 247     if ($BondStereo && $BondStereo != 0) {
 248       _ProcessBondStereo($Bond, $BondStereo);
 249     }
 250 
 251     $Molecule->AddBond($Bond);
 252   }
 253 
 254   # Process available property block lines starting with M CHG, M ISO and M RAD. All other property blocks
 255   # lines are for query or specific display purposes and are ignored for now.
 256   #
 257   my($PropertyLineIndex, $PropertyLine, $FirstChargeOrRadicalLine, @ValuePairs);
 258 
 259   $PropertyLineIndex = $FirstAtomLineIndex + $AtomCount + $BondCount;
 260   $PropertyLine = $MoleculeLines[$PropertyLineIndex];
 261   $FirstChargeOrRadicalLine = 1;
 262 
 263   PROPERTYLINE: while ($PropertyLine !~ /^M  END/i ) {
 264     if ($PropertyLine =~ /\$\$\$\$/) {
 265       last PROPERTYLINE;
 266     }
 267     if ($PropertyLine =~ /^(M  CHG|M  RAD)/i) {
 268       if ($FirstChargeOrRadicalLine) {
 269 	$FirstChargeOrRadicalLine = 0;
 270 	_ZeroOutAtomsChargeAndRadicalValues(\%AtomNumToAtomMap);
 271       }
 272       if ($PropertyLine =~ /^M  CHG/i) {
 273 	@ValuePairs = SDFileUtil::ParseCmpdChargePropertyLine($PropertyLine);
 274 	_ProcessChargeProperty(\@ValuePairs, \%AtomNumToAtomMap);
 275       }
 276       elsif ($PropertyLine =~ /^M  RAD/i) {
 277 	@ValuePairs = SDFileUtil::ParseCmpdRadicalPropertyLine($PropertyLine);
 278 	_ProcessRadicalProperty(\@ValuePairs, \%AtomNumToAtomMap);
 279       }
 280     }
 281     elsif ($PropertyLine =~ /^M  ISO/i) {
 282       @ValuePairs = SDFileUtil::ParseCmpdIsotopePropertyLine($PropertyLine);
 283       _ProcessIsotopeProperty(\@ValuePairs, \%AtomNumToAtomMap);
 284     }
 285     $PropertyLineIndex++;
 286     $PropertyLine = $MoleculeLines[$PropertyLineIndex];
 287   }
 288   # Store input molecule string as generic property of molecule...
 289   $Molecule->SetMDLCmpdString($MoleculeString);
 290 
 291   return $Molecule;
 292 }
 293 
 294 # Generate molecule string using molecule object...
 295 sub GenerateMoleculeString {
 296   my($FirstParameter, $SecondParameter) = @_;
 297   my($This, $Molecule);
 298 
 299   if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) {
 300     ($This, $Molecule) = ($FirstParameter, $SecondParameter);
 301   }
 302   else {
 303     $Molecule = $FirstParameter;
 304     $This = undef;
 305   }
 306   if (!defined($Molecule)) {
 307     return undef;
 308   }
 309   my(@MoleculeLines);
 310   @MoleculeLines = ();
 311 
 312   # First line: Molname line...
 313   push @MoleculeLines, SDFileUtil::GenerateCmpdMolNameLine($Molecule->GetName());
 314 
 315   # Second line: Misc info...
 316   my($ProgramName, $UserInitial, $Code);
 317   $ProgramName = ''; $UserInitial = ''; $Code = '';
 318   $Code = $Molecule->IsThreeDimensional() ? '3D' : '2D';
 319 
 320   push @MoleculeLines, SDFileUtil::GenerateCmpdMiscInfoLine($ProgramName, $UserInitial, $Code);
 321 
 322   # Third line: Comments line...
 323   my($Comments);
 324   $Comments = $Molecule->HasProperty('MDLComments') ? $Molecule->GetMDLComments() : ($Molecule->HasProperty('Comments') ? $Molecule->GetComments() : '');
 325   push @MoleculeLines, SDFileUtil::GenerateCmpdCommentsLine($Comments);
 326 
 327   # Fourth line: Counts line for V2000
 328   my($AtomCount, $BondCount, $ChiralFlag);
 329   $AtomCount = $Molecule->GetNumOfAtoms();
 330   $BondCount = $Molecule->GetNumOfBonds();
 331   $ChiralFlag = 0;
 332   push @MoleculeLines, SDFileUtil::GenerateCmpdCountsLine($AtomCount, $BondCount, $ChiralFlag);
 333 
 334   # Atom lines...
 335   my($Atom, $AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity, $AtomNum, $AtomID, @Atoms, %AtomIDToNum);
 336   my($ChargePropertyValue, $IsotopePropertyValue, $RadicalPropertyValue, @IsotopePropertyValuePairs, @ChargePropertyValuePairs, @RadicalPropertyValuePairs);
 337 
 338   @ChargePropertyValuePairs = ();
 339   @IsotopePropertyValuePairs = ();
 340   @RadicalPropertyValuePairs = ();
 341 
 342   @Atoms = $Molecule->GetAtoms();
 343 
 344   $AtomNum = 0;
 345   for $Atom (@Atoms) {
 346     $AtomNum++;
 347     $AtomID = $Atom->GetID();
 348     $AtomIDToNum{$AtomID} = $AtomNum;
 349 
 350     $AtomSymbol = $Atom->GetAtomSymbol();
 351     ($AtomX, $AtomY, $AtomZ) = $Atom->GetXYZ();
 352 
 353     # Setup mass difference...
 354     $MassDifference = _GetMassDifference($Atom);
 355     if ($MassDifference) {
 356       # Hold it for M  ISO property lines...
 357       $IsotopePropertyValue = _GetIsotopePropertyValue($Atom);
 358       if ($IsotopePropertyValue) {
 359 	push @IsotopePropertyValuePairs, ($AtomNum, $IsotopePropertyValue);
 360       }
 361     }
 362 
 363     # Setup charge...
 364     $Charge = _GetCharge($Atom);
 365     if ($Charge) {
 366       # Hold it for M  CHG property lines...
 367       $ChargePropertyValue = _GetChargePropertyValue($Atom);
 368       if ($ChargePropertyValue) {
 369 	push @ChargePropertyValuePairs, ($AtomNum, $ChargePropertyValue);
 370       }
 371     }
 372 
 373     # Hold any radical values for  for M  RAD property lines...
 374     $RadicalPropertyValue = _GetRadicalPropertyValue($Atom);
 375     if ($RadicalPropertyValue) {
 376       push @RadicalPropertyValuePairs, ($AtomNum, $RadicalPropertyValue);
 377     }
 378 
 379     # Setup stereo parity...
 380     $StereoParity = _GetStereoParity($Atom);
 381 
 382     push @MoleculeLines, SDFileUtil::GenerateCmpdAtomLine($AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity);
 383   }
 384 
 385   # Bond lines...
 386   my($FirstAtomID, $FirstAtom, $FirstAtomNum, $SecondAtomID, $SecondAtom, $SecondAtomNum, $MDLBondType, $BondOrder, $MDLBondStereo, $Bond, @Bonds);
 387   for $FirstAtom (@Atoms) {
 388     $FirstAtomID = $FirstAtom->GetID();
 389     $FirstAtomNum = $AtomIDToNum{$FirstAtomID};
 390 
 391     @Bonds = ();
 392     @Bonds = $FirstAtom->GetBonds();
 393     BOND: for $Bond (@Bonds) {
 394       $SecondAtom = $Bond->GetBondedAtom($FirstAtom);
 395       $SecondAtomID = $SecondAtom->GetID();
 396       $SecondAtomNum = $AtomIDToNum{$SecondAtomID};
 397       if ($FirstAtomNum >= $SecondAtomNum) {
 398 	next BOND;
 399       }
 400       # Setup BondType...
 401       $BondOrder = $Bond->GetBondOrder();
 402       $MDLBondType = SDFileUtil::InternalBondOrderToMDLBondType($BondOrder);
 403 
 404       # Setup BondStereo...
 405       $MDLBondStereo = _GetBondStereo($Bond);
 406 
 407       push @MoleculeLines, SDFileUtil::GenerateCmpdBondLine($FirstAtomNum, $SecondAtomNum, $MDLBondType, $MDLBondStereo);
 408     }
 409   }
 410   # Property lines...
 411   if (@IsotopePropertyValuePairs) {
 412     push @MoleculeLines, SDFileUtil::GenerateCmpdIsotopePropertyLines(\@IsotopePropertyValuePairs);
 413   }
 414   if (@ChargePropertyValuePairs) {
 415     push @MoleculeLines, SDFileUtil::GenerateCmpdChargePropertyLines(\@ChargePropertyValuePairs);
 416   }
 417   if (@RadicalPropertyValuePairs) {
 418     push @MoleculeLines, SDFileUtil::GenerateCmpdRadicalPropertyLines(\@RadicalPropertyValuePairs);
 419   }
 420 
 421   push @MoleculeLines, "M  END";
 422 
 423   return join "\n", @MoleculeLines;
 424 }
 425 
 426 # Process MassDifference value and set atom's mass number...
 427 #
 428 sub _ProcessMassDifference {
 429   my($Atom, $MassDifference) = @_;
 430   my($MassNumber, $NewMassNumber, $AtomicNumber);
 431 
 432   $AtomicNumber = $Atom->GetAtomicNumber();
 433 
 434   if (!$AtomicNumber) {
 435     carp "Warning: ${ClassName}->_ProcessMassDifference: Ignoring specified mass difference value, $MassDifference, in SD file: Assigned to non standard element...";
 436     return;
 437   }
 438   $MassNumber = $Atom->GetMassNumber();
 439   if (!$MassDifference) {
 440     carp "Warning: ${ClassName}->_ProcessMassDifference: Ignoring specified mass difference value, $MassDifference, in SD file: Unknown MassNumber value...";
 441     return;
 442   }
 443   $NewMassNumber = $MassNumber + $MassDifference;
 444   if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $NewMassNumber)) {
 445     my($AtomSymbol) = $Atom->GetAtomSymbol();
 446     carp "Warning: ${ClassName}->_ProcessMassDifference: Unknown mass number, $MassNumber, corresponding to specified mass difference value, $MassDifference, in SD for atom with atomic number, $AtomicNumber, and atomic symbol, $AtomSymbol. The mass number value has been assigned. Don't forget to Set ExactMass property explicitly; otherwise, GetExactMass method would return mass of most abundant isotope...\n";
 447   }
 448 
 449   # Use SetProperty method instead of SetMassNumber to skip explicit checks on MassNumber value...
 450   $Atom->SetProperty('MassNumber', $NewMassNumber);
 451 }
 452 
 453 # Get mass difference value...
 454 sub _GetMassDifference {
 455   my($Atom) = @_;
 456   my($MassDifference, $MassNumber, $MostAbundantMassNumber, $AtomicNumber);
 457 
 458   $MassDifference = 0;
 459   $MassNumber = $Atom->GetMassNumber();
 460   if (defined $MassNumber) {
 461     $AtomicNumber = $Atom->GetAtomicNumber();
 462     if (defined $AtomicNumber) {
 463       $MostAbundantMassNumber = PeriodicTable::GetElementMostAbundantNaturalIsotopeMassNumber($AtomicNumber);
 464       if ($MassNumber != $MostAbundantMassNumber) {
 465 	$MassDifference = $MassNumber - $MostAbundantMassNumber;
 466       }
 467     }
 468   }
 469   return $MassDifference;
 470 }
 471 
 472 # Process formal charge value and assign it to atom as formal charge...
 473 sub _ProcessCharge {
 474   my($Atom, $Charge) = @_;
 475   my($InternalCharge);
 476 
 477   $InternalCharge = SDFileUtil::MDLChargeToInternalCharge($Charge);
 478   $Atom->SetFormalCharge($InternalCharge);
 479 }
 480 
 481 # Get MDL formal charge value ...
 482 sub _GetCharge {
 483   my($Atom) = @_;
 484   my($InternalCharge, $Charge);
 485 
 486   $Charge = 0;
 487   if ($Atom->HasProperty('FormalCharge')) {
 488     $InternalCharge = $Atom->GetFormalCharge();
 489     if ($InternalCharge) {
 490       $Charge = SDFileUtil::InternalChargeToMDLCharge($InternalCharge);
 491     }
 492   }
 493   return $Charge;
 494 }
 495 
 496 # Process stereo parity value and assign it to atom as MDL property...
 497 #
 498 # Notes:
 499 #   . Mark atom as chiral center
 500 #   . Assign any explicit Clockwise (parity 1), CounterClockwise (parity 2) or either value (parity 3) as property of atom.
 501 #   . MDL values of Clockwise and CounterClockwise don't correspond to priority assigned to ligands around
 502 #     stereo center using CIP scheme; consequently, these values can't be used to set internal Stereochemistry for
 503 #     an atom.
 504 #
 505 sub _ProcessStereoParity {
 506   my($Atom, $StereoParity) = @_;
 507 
 508   $Atom->SetStereoCenter('1');
 509   $Atom->SetMDLStereoParity($StereoParity);
 510 }
 511 
 512 # Set stereo parity value to zero for now: The current release of MayaChemTools hasn't implemented
 513 # functionality to determine chirality.
 514 #
 515 sub _GetStereoParity {
 516   my($Atom) = @_;
 517   my($StereoParity);
 518 
 519   $StereoParity = 0;
 520 
 521   return $StereoParity;
 522 }
 523 
 524 # Process bond stereo value...
 525 sub _ProcessBondStereo {
 526   my($Bond, $BondStereo) = @_;
 527   my($InternalBondType);
 528 
 529   $InternalBondType = SDFileUtil::MDLBondStereoToInternalBondType($BondStereo);
 530   if ($InternalBondType) {
 531     $Bond->SetBondType($InternalBondType);
 532   }
 533 }
 534 
 535 # Get MDLBondStereo value...
 536 sub _GetBondStereo {
 537   my($Bond) = @_;
 538   my($InternalBondType, $BondStereo);
 539 
 540   $BondStereo = 0;
 541   $InternalBondType = $Bond->GetBondType();
 542   if ($InternalBondType) {
 543     $BondStereo = SDFileUtil::InternalBondTypeToMDLBondStereo($InternalBondType);
 544   }
 545 
 546   return $BondStereo;
 547 }
 548 
 549 # Zero out charge and radical values specified for atoms...
 550 sub _ZeroOutAtomsChargeAndRadicalValues {
 551   my($AtomNumToAtomMapRef) = @_;
 552   my($Atom);
 553 
 554   for $Atom (values %{$AtomNumToAtomMapRef}) {
 555     if ($Atom->HasProperty('FormalCharge')) {
 556       $Atom->DeleteProperty('FormalCharge');
 557     }
 558     elsif ($Atom->HasProperty('SpinMultiplicity')) {
 559       $Atom->DeleteProperty('SpinMultiplicity');
 560     }
 561   }
 562 }
 563 
 564 # Process charge property value pairs...
 565 sub _ProcessChargeProperty {
 566   my($ValuePairsRef, $AtomNumToAtomMapRef) = @_;
 567 
 568   if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) {
 569     return;
 570   }
 571   my($Index, $ValuePairsCount, $AtomNum, $Charge, $Atom);
 572 
 573   $ValuePairsCount = scalar @{$ValuePairsRef};
 574   VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) {
 575     $AtomNum = $ValuePairsRef->[$Index]; $Charge = $ValuePairsRef->[$Index + 1];
 576     if (!$Charge) {
 577       next VALUEPAIRS;
 578     }
 579     if (!exists $AtomNumToAtomMapRef->{$AtomNum}) {
 580       next VALUEPAIRS;
 581     }
 582     $Atom = $AtomNumToAtomMapRef->{$AtomNum};
 583     if ($Atom->HasProperty('SpinMultiplicity')) {
 584       carp "Warning: ${ClassName}->_ProcessChargeProperty: Setting formal charge on atom number, $AtomNum,  with already assigned spin multiplicity value...";
 585     }
 586     $Atom->SetFormalCharge($Charge);
 587   }
 588 }
 589 
 590 # Get charge property value for an atom...
 591 sub _GetChargePropertyValue {
 592   my($Atom) = @_;
 593   my($Charge);
 594 
 595   $Charge = 0;
 596   if ($Atom->HasProperty('FormalCharge')) {
 597     $Charge = $Atom->GetFormalCharge();
 598   }
 599   return $Charge;
 600 }
 601 
 602 # Process charge property value pairs...
 603 sub _ProcessRadicalProperty {
 604   my($ValuePairsRef, $AtomNumToAtomMapRef) = @_;
 605 
 606   if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) {
 607     return;
 608   }
 609   my($Index, $ValuePairsCount, $AtomNum, $Radical, $SpinMultiplicity, $Atom);
 610 
 611   $ValuePairsCount = scalar @{$ValuePairsRef};
 612   VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) {
 613     $AtomNum = $ValuePairsRef->[$Index]; $Radical = $ValuePairsRef->[$Index + 1];
 614     if (!$Radical) {
 615       next VALUEPAIRS;
 616     }
 617     if (!exists $AtomNumToAtomMapRef->{$AtomNum}) {
 618       next VALUEPAIRS;
 619     }
 620     $Atom = $AtomNumToAtomMapRef->{$AtomNum};
 621     if ($Atom->HasProperty('FormalCharge')) {
 622       carp "Warning: ${ClassName}->_ProcessRadicalProperty: Setting spin multiplicity on atom number, $AtomNum,  with already assigned formal charge value...";
 623     }
 624     $SpinMultiplicity = SDFileUtil::MDLRadicalToInternalSpinMultiplicity($Radical);
 625     $Atom->SetSpinMultiplicity($SpinMultiplicity);
 626   }
 627 }
 628 
 629 # Get radical property value for an atom...
 630 sub _GetRadicalPropertyValue {
 631   my($Atom) = @_;
 632   my($Radical, $SpinMultiplicity);
 633 
 634   $Radical = 0;
 635   if ($Atom->HasProperty('SpinMultiplicity')) {
 636     $SpinMultiplicity = $Atom->GetSpinMultiplicity();
 637     $Radical = SDFileUtil::InternalSpinMultiplicityToMDLRadical($SpinMultiplicity);
 638   }
 639   return $Radical;
 640 }
 641 
 642 # Process isotope property value pairs...
 643 sub _ProcessIsotopeProperty {
 644   my($ValuePairsRef, $AtomNumToAtomMapRef) = @_;
 645 
 646   if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) {
 647     return;
 648   }
 649   my($Index, $ValuePairsCount, $AtomNum, $MassNumber, $Atom, $AtomicNumber);
 650 
 651   $ValuePairsCount = scalar @{$ValuePairsRef};
 652   VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) {
 653     $AtomNum = $ValuePairsRef->[$Index]; $MassNumber = $ValuePairsRef->[$Index + 1];
 654     if (!$MassNumber) {
 655       next VALUEPAIRS;
 656     }
 657     if (!exists $AtomNumToAtomMapRef->{$AtomNum}) {
 658       next VALUEPAIRS;
 659     }
 660     $Atom = $AtomNumToAtomMapRef->{$AtomNum};
 661     $AtomicNumber = $Atom->GetAtomicNumber();
 662 
 663     if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $MassNumber)) {
 664       my($AtomSymbol) = $Atom->GetAtomSymbol();
 665       carp "Warning: ${ClassName}->_ProcessProcessIsotopeProperty: Unknown mass number, $MassNumber, specified on M  ISO property line for atom number, $AtomNum,  in SD for atom with atomic number, $AtomicNumber, and atomic symbol, $AtomSymbol. The mass number value has been assigned. Don't forget to Set ExactMass property explicitly; otherwise, GetExactMass method would return mass of most abundant isotope...\n";
 666     }
 667 
 668     # Use SetProperty method instead of SetMassNumber to skip explicit checks on MassNumber value...
 669     $Atom->SetProperty('MassNumber', $MassNumber);
 670   }
 671 }
 672 
 673 # Get isotope property value for an atom...
 674 sub _GetIsotopePropertyValue {
 675   my($Atom) = @_;
 676   my($MassNumber);
 677 
 678   $MassNumber = 0;
 679   if ($Atom->HasProperty('MassNumber')) {
 680     $MassNumber = $Atom->GetMassNumber();
 681   }
 682   return $MassNumber;
 683 }
 684 
 685 # Is it a MDLMolFileIO object?
 686 sub _IsMDLMolFileIO {
 687   my($Object) = @_;
 688 
 689   return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
 690 }
 691