MayaChemTools

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