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