1 package FileIO::MDLMolFileIO; 2 # 3 # $RCSfile: MDLMolFileIO.pm,v $ 4 # $Date: 2015/02/28 20:48:43 $ 5 # $Revision: 1.32 $ 6 # 7 # Author: Manish Sud <msud@san.rr.com> 8 # 9 # Copyright (C) 2015 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::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 object... 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 # Set valence model for calculating implicit hydrogens... 186 $Molecule->SetValenceModel('MDLValenceModel'); 187 188 # Process headers data... 189 $LineIndex = 0; 190 my($MoleculeName) = SDFileUtil::ParseCmpdMolNameLine($MoleculeLines[$LineIndex]); 191 $MoleculeName = TextUtil::RemoveTrailingWhiteSpaces($MoleculeName); 192 $Molecule->SetName($MoleculeName); 193 194 $LineIndex++; 195 my($UserInitial, $ProgramName, $Date, $Code, $ScalingFactor1, $ScalingFactor2, $Energy, $RegistryNum) = SDFileUtil::ParseCmpdMiscInfoLine($MoleculeLines[$LineIndex]); 196 $Molecule->SetProperties('MDLUserInitial' => $UserInitial, 'MDLProgramName' => $ProgramName, 'MDLDate' => $Date, 'MDLCode' => $Code, 'MDLScalingFactor1' => $ScalingFactor1, 'MDLScalingFactor2' => $ScalingFactor2, 'MDLEnergy' => $Energy, 'MDLRegistryNum' => $RegistryNum); 197 198 $LineIndex++; 199 my($Comments) = SDFileUtil::ParseCmpdCommentsLine($MoleculeLines[$LineIndex]); 200 $Molecule->SetProperties('MDLComments' => $Comments); 201 202 $LineIndex++; 203 my($AtomCount, $BondCount, $ChiralFlag, $PropertyCount, $Version) = SDFileUtil::ParseCmpdCountsLine($MoleculeLines[$LineIndex]); 204 205 $Molecule->SetProperties('MDLChiralFlag' => $ChiralFlag, 'MDLPropertyCount' => $PropertyCount, 'MDLVersion' => $Version); 206 207 # Process atom data... 208 my($FirstAtomLineIndex, $LastAtomLineIndex, $AtomNum, $AtomX, $AtomY, $AtomZ, $AtomSymbol, $MassDifference, $Charge, $StereoParity, $Atom, %AtomNumToAtomMap); 209 210 $AtomNum = 0; 211 %AtomNumToAtomMap = (); 212 $FirstAtomLineIndex = 4; $LastAtomLineIndex = $FirstAtomLineIndex + $AtomCount - 1; 213 214 for ($LineIndex = $FirstAtomLineIndex; $LineIndex <= $LastAtomLineIndex; $LineIndex++) { 215 $AtomNum++; 216 ($AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity) = SDFileUtil::ParseCmpdAtomLine($MoleculeLines[$LineIndex]); 217 218 $Atom = new Atom('AtomSymbol' => $AtomSymbol, 'XYZ' => [$AtomX, $AtomY, $AtomZ]); 219 220 if ($MassDifference && $MassDifference != 0) { 221 _ProcessMassDifference($Atom, $MassDifference); 222 } 223 if ($Charge && $Charge != 0) { 224 _ProcessCharge($Atom, $Charge); 225 } 226 if ($StereoParity && $StereoParity != 0) { 227 _ProcessStereoParity($Atom, $StereoParity); 228 } 229 230 $AtomNumToAtomMap{$AtomNum} = $Atom; 231 $Molecule->AddAtom($Atom); 232 } 233 234 # Process bond data... 235 my($FirstBondLineIndex, $LastBondLineIndex, $FirstAtomNum, $SecondAtomNum, $BondType, $BondStereo, $InternalBondOrder, $InternalBondType, $Bond, $Atom1, $Atom2); 236 237 $FirstBondLineIndex = $FirstAtomLineIndex + $AtomCount; 238 $LastBondLineIndex = $FirstAtomLineIndex + $AtomCount + $BondCount - 1; 239 240 for ($LineIndex = $FirstBondLineIndex; $LineIndex <= $LastBondLineIndex; $LineIndex++) { 241 ($FirstAtomNum, $SecondAtomNum, $BondType, $BondStereo) = SDFileUtil::ParseCmpdBondLine($MoleculeLines[$LineIndex]); 242 243 $Atom1 = $AtomNumToAtomMap{$FirstAtomNum}; 244 $Atom2 = $AtomNumToAtomMap{$SecondAtomNum}; 245 246 ($InternalBondOrder, $InternalBondType) = SDFileUtil::MDLBondTypeToInternalBondOrder($BondType); 247 $Bond = new Bond('Atoms' => [$Atom1, $Atom2], 'BondOrder' => $InternalBondOrder); 248 $Bond->SetBondType($InternalBondType); 249 250 if ($BondStereo && $BondStereo != 0) { 251 _ProcessBondStereo($Bond, $BondStereo); 252 } 253 254 $Molecule->AddBond($Bond); 255 } 256 257 # Process available property block lines starting with A aaa, M CHG, M ISO and M RAD. All other property blocks 258 # lines are for query or specific display purposes and are ignored for now. 259 # 260 # 261 my($PropertyLineIndex, $PropertyLine, $FirstChargeOrRadicalLine, @ValuePairs); 262 263 $PropertyLineIndex = $FirstAtomLineIndex + $AtomCount + $BondCount; 264 $PropertyLine = $MoleculeLines[$PropertyLineIndex]; 265 $FirstChargeOrRadicalLine = 1; 266 267 PROPERTYLINE: while ($PropertyLine !~ /^M END/i ) { 268 if ($PropertyLine =~ /\$\$\$\$/) { 269 last PROPERTYLINE; 270 } 271 if ($PropertyLine =~ /^(M CHG|M RAD)/i) { 272 if ($FirstChargeOrRadicalLine) { 273 $FirstChargeOrRadicalLine = 0; 274 _ZeroOutAtomsChargeAndRadicalValues(\%AtomNumToAtomMap); 275 } 276 if ($PropertyLine =~ /^M CHG/i) { 277 @ValuePairs = SDFileUtil::ParseCmpdChargePropertyLine($PropertyLine); 278 _ProcessChargeProperty(\@ValuePairs, \%AtomNumToAtomMap); 279 } 280 elsif ($PropertyLine =~ /^M RAD/i) { 281 @ValuePairs = SDFileUtil::ParseCmpdRadicalPropertyLine($PropertyLine); 282 _ProcessRadicalProperty(\@ValuePairs, \%AtomNumToAtomMap); 283 } 284 } 285 elsif ($PropertyLine =~ /^M ISO/i) { 286 @ValuePairs = SDFileUtil::ParseCmpdIsotopePropertyLine($PropertyLine); 287 _ProcessIsotopeProperty(\@ValuePairs, \%AtomNumToAtomMap); 288 } 289 elsif ($PropertyLine =~ /^A /i) { 290 my($NextPropertyLine); 291 $PropertyLineIndex++; 292 $NextPropertyLine = $MoleculeLines[$PropertyLineIndex]; 293 @ValuePairs = SDFileUtil::ParseCmpdAtomAliasPropertyLine($PropertyLine, $NextPropertyLine); 294 _ProcessAtomAliasProperty(\@ValuePairs, \%AtomNumToAtomMap); 295 } 296 $PropertyLineIndex++; 297 $PropertyLine = $MoleculeLines[$PropertyLineIndex]; 298 } 299 # Store input molecule string as generic property of molecule... 300 $Molecule->SetInputMoleculeString($MoleculeString); 301 302 return $Molecule; 303 } 304 305 # Generate molecule string using molecule object... 306 sub GenerateMoleculeString { 307 my($FirstParameter, $SecondParameter) = @_; 308 my($This, $Molecule); 309 310 if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) { 311 ($This, $Molecule) = ($FirstParameter, $SecondParameter); 312 } 313 else { 314 $Molecule = $FirstParameter; 315 $This = undef; 316 } 317 if (!defined($Molecule)) { 318 return undef; 319 } 320 my(@MoleculeLines); 321 @MoleculeLines = (); 322 323 # First line: Molname line... 324 push @MoleculeLines, SDFileUtil::GenerateCmpdMolNameLine($Molecule->GetName()); 325 326 # Second line: Misc info... 327 my($ProgramName, $UserInitial, $Code); 328 $ProgramName = ''; $UserInitial = ''; $Code = ''; 329 330 $Code = $Molecule->IsThreeDimensional() ? '3D' : '2D'; 331 332 push @MoleculeLines, SDFileUtil::GenerateCmpdMiscInfoLine($ProgramName, $UserInitial, $Code); 333 334 # Third line: Comments line... 335 my($Comments); 336 $Comments = $Molecule->HasProperty('MDLComments') ? $Molecule->GetMDLComments() : ($Molecule->HasProperty('Comments') ? $Molecule->GetComments() : ''); 337 push @MoleculeLines, SDFileUtil::GenerateCmpdCommentsLine($Comments); 338 339 # Fourth line: Counts line for V2000 340 my($AtomCount, $BondCount, $ChiralFlag); 341 $AtomCount = $Molecule->GetNumOfAtoms(); 342 $BondCount = $Molecule->GetNumOfBonds(); 343 $ChiralFlag = 0; 344 push @MoleculeLines, SDFileUtil::GenerateCmpdCountsLine($AtomCount, $BondCount, $ChiralFlag); 345 346 # Atom lines... 347 my($Atom, $AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity, $AtomNum, $AtomID, @Atoms, %AtomIDToNum); 348 my($ChargePropertyValue, $IsotopePropertyValue, $RadicalPropertyValue, $AtomAliasPropertyValue, @IsotopePropertyValuePairs, @ChargePropertyValuePairs, @RadicalPropertyValuePairs, @AtomAliasPropertyValuePairs); 349 350 @ChargePropertyValuePairs = (); 351 @IsotopePropertyValuePairs = (); 352 @RadicalPropertyValuePairs = (); 353 @AtomAliasPropertyValuePairs = (); 354 355 @Atoms = $Molecule->GetAtoms(); 356 357 $AtomNum = 0; 358 for $Atom (@Atoms) { 359 $AtomNum++; 360 $AtomID = $Atom->GetID(); 361 $AtomIDToNum{$AtomID} = $AtomNum; 362 363 $AtomSymbol = $Atom->GetAtomSymbol(); 364 ($AtomX, $AtomY, $AtomZ) = $Atom->GetXYZ(); 365 366 # Setup mass difference... 367 $MassDifference = _GetMassDifference($Atom); 368 if ($MassDifference) { 369 # Hold it for M ISO property lines... 370 $IsotopePropertyValue = _GetIsotopePropertyValue($Atom); 371 if ($IsotopePropertyValue) { 372 push @IsotopePropertyValuePairs, ($AtomNum, $IsotopePropertyValue); 373 } 374 } 375 376 # Setup charge... 377 $Charge = _GetCharge($Atom); 378 if ($Charge) { 379 # Hold it for M CHG property lines... 380 $ChargePropertyValue = _GetChargePropertyValue($Atom); 381 if ($ChargePropertyValue) { 382 push @ChargePropertyValuePairs, ($AtomNum, $ChargePropertyValue); 383 } 384 } 385 386 # Hold any radical values for for M RAD property lines... 387 $RadicalPropertyValue = _GetRadicalPropertyValue($Atom); 388 if ($RadicalPropertyValue) { 389 push @RadicalPropertyValuePairs, ($AtomNum, $RadicalPropertyValue); 390 } 391 392 # Hold any atom alias value for A xxx property lines.... 393 $AtomAliasPropertyValue = _GetAtomAliasPropertyValue($Atom); 394 if ($AtomAliasPropertyValue) { 395 push @AtomAliasPropertyValuePairs, ($AtomNum, $AtomAliasPropertyValue); 396 397 # Set AtomSymbol to carbon as atom alias would override its value during parsing... 398 $AtomSymbol = "C"; 399 } 400 401 # Setup stereo parity... 402 $StereoParity = _GetStereoParity($Atom); 403 404 push @MoleculeLines, SDFileUtil::GenerateCmpdAtomLine($AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity); 405 } 406 407 # Bond lines... 408 my($FirstAtomID, $FirstAtom, $FirstAtomNum, $SecondAtomID, $SecondAtom, $SecondAtomNum, $MDLBondType, $BondOrder, $BondType, $MDLBondStereo, $Bond, @Bonds); 409 for $FirstAtom (@Atoms) { 410 $FirstAtomID = $FirstAtom->GetID(); 411 $FirstAtomNum = $AtomIDToNum{$FirstAtomID}; 412 413 @Bonds = (); 414 @Bonds = $FirstAtom->GetBonds(); 415 BOND: for $Bond (@Bonds) { 416 $SecondAtom = $Bond->GetBondedAtom($FirstAtom); 417 $SecondAtomID = $SecondAtom->GetID(); 418 $SecondAtomNum = $AtomIDToNum{$SecondAtomID}; 419 if ($FirstAtomNum >= $SecondAtomNum) { 420 next BOND; 421 } 422 # Setup BondType... 423 $BondOrder = $Bond->GetBondOrder(); 424 $BondType = $Bond->GetBondType(); 425 $MDLBondType = SDFileUtil::InternalBondOrderToMDLBondType($BondOrder, $BondType); 426 427 # Setup BondStereo... 428 $MDLBondStereo = _GetBondStereo($Bond); 429 430 push @MoleculeLines, SDFileUtil::GenerateCmpdBondLine($FirstAtomNum, $SecondAtomNum, $MDLBondType, $MDLBondStereo); 431 } 432 } 433 # Property lines... 434 if (@IsotopePropertyValuePairs) { 435 push @MoleculeLines, SDFileUtil::GenerateCmpdIsotopePropertyLines(\@IsotopePropertyValuePairs); 436 } 437 if (@ChargePropertyValuePairs) { 438 push @MoleculeLines, SDFileUtil::GenerateCmpdChargePropertyLines(\@ChargePropertyValuePairs); 439 } 440 if (@RadicalPropertyValuePairs) { 441 push @MoleculeLines, SDFileUtil::GenerateCmpdRadicalPropertyLines(\@RadicalPropertyValuePairs); 442 } 443 if (@AtomAliasPropertyValuePairs) { 444 push @MoleculeLines, SDFileUtil::GenerateCmpdAtomAliasPropertyLines(\@AtomAliasPropertyValuePairs); 445 } 446 447 push @MoleculeLines, "M END"; 448 449 return join "\n", @MoleculeLines; 450 } 451 452 # Process MassDifference value and set atom's mass number... 453 # 454 sub _ProcessMassDifference { 455 my($Atom, $MassDifference) = @_; 456 my($MassNumber, $NewMassNumber, $AtomicNumber); 457 458 $AtomicNumber = $Atom->GetAtomicNumber(); 459 460 if (!$AtomicNumber) { 461 carp "Warning: ${ClassName}->_ProcessMassDifference: Ignoring specified mass difference value, $MassDifference, in SD file: Assigned to non standard element..."; 462 return; 463 } 464 $MassNumber = $Atom->GetMassNumber(); 465 if (!$MassDifference) { 466 carp "Warning: ${ClassName}->_ProcessMassDifference: Ignoring specified mass difference value, $MassDifference, in SD file: Unknown MassNumber value..."; 467 return; 468 } 469 $NewMassNumber = $MassNumber + $MassDifference; 470 if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $NewMassNumber)) { 471 my($AtomSymbol) = $Atom->GetAtomSymbol(); 472 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"; 473 } 474 475 # Use SetProperty method instead of SetMassNumber to skip explicit checks on MassNumber value... 476 $Atom->SetProperty('MassNumber', $NewMassNumber); 477 } 478 479 # Get mass difference value... 480 sub _GetMassDifference { 481 my($Atom) = @_; 482 my($MassDifference, $MassNumber, $MostAbundantMassNumber, $AtomicNumber); 483 484 $MassDifference = 0; 485 $MassNumber = $Atom->GetMassNumber(); 486 if (defined $MassNumber) { 487 $AtomicNumber = $Atom->GetAtomicNumber(); 488 if (defined $AtomicNumber) { 489 $MostAbundantMassNumber = PeriodicTable::GetElementMostAbundantNaturalIsotopeMassNumber($AtomicNumber); 490 if (defined($MostAbundantMassNumber) && $MassNumber != $MostAbundantMassNumber) { 491 $MassDifference = $MassNumber - $MostAbundantMassNumber; 492 } 493 } 494 } 495 return $MassDifference; 496 } 497 498 # Process formal charge value and assign it to atom as formal charge... 499 sub _ProcessCharge { 500 my($Atom, $Charge) = @_; 501 my($InternalCharge); 502 503 $InternalCharge = SDFileUtil::MDLChargeToInternalCharge($Charge); 504 $Atom->SetFormalCharge($InternalCharge); 505 } 506 507 # Get MDL formal charge value ... 508 sub _GetCharge { 509 my($Atom) = @_; 510 my($InternalCharge, $Charge); 511 512 $Charge = 0; 513 if ($Atom->HasProperty('FormalCharge')) { 514 $InternalCharge = $Atom->GetFormalCharge(); 515 if ($InternalCharge) { 516 $Charge = SDFileUtil::InternalChargeToMDLCharge($InternalCharge); 517 } 518 } 519 return $Charge; 520 } 521 522 # Process stereo parity value and assign it to atom as MDL property... 523 # 524 # Notes: 525 # . Mark atom as chiral center 526 # . Assign any explicit Clockwise (parity 1), CounterClockwise (parity 2) or either value (parity 3) as property of atom. 527 # . MDL values of Clockwise and CounterClockwise don't correspond to priority assigned to ligands around 528 # stereo center using CIP scheme; consequently, these values can't be used to set internal Stereochemistry for 529 # an atom. 530 # 531 sub _ProcessStereoParity { 532 my($Atom, $StereoParity) = @_; 533 534 $Atom->SetStereoCenter('1'); 535 $Atom->SetMDLStereoParity($StereoParity); 536 } 537 538 # Set stereo parity value to zero for now: The current release of MayaChemTools hasn't implemented 539 # functionality to determine chirality. 540 # 541 sub _GetStereoParity { 542 my($Atom) = @_; 543 my($StereoParity); 544 545 $StereoParity = 0; 546 547 return $StereoParity; 548 } 549 550 # Process bond stereo value... 551 sub _ProcessBondStereo { 552 my($Bond, $BondStereo) = @_; 553 my($InternalBondStereo); 554 555 $InternalBondStereo = SDFileUtil::MDLBondStereoToInternalBondStereochemistry($BondStereo); 556 if ($InternalBondStereo) { 557 $Bond->SetBondStereochemistry($InternalBondStereo); 558 } 559 } 560 561 # Get MDLBondStereo value... 562 sub _GetBondStereo { 563 my($Bond) = @_; 564 my($InternalBondStereo, $BondStereo); 565 566 $BondStereo = 0; 567 568 $InternalBondStereo = ''; 569 BONDSTEREO: { 570 if ($Bond->IsUp()) { 571 $InternalBondStereo = 'Up'; 572 last BONDSTEREO; 573 } 574 if ($Bond->IsDown()) { 575 $InternalBondStereo = 'Down'; 576 last BONDSTEREO; 577 } 578 if ($Bond->IsUpOrDown()) { 579 $InternalBondStereo = 'UpOrDown'; 580 last BONDSTEREO; 581 } 582 if ($Bond->IsCisOrTrans() || $Bond->IsCis() || $Bond->IsTrans()) { 583 $InternalBondStereo = 'CisOrTrans'; 584 last BONDSTEREO; 585 } 586 $InternalBondStereo = ''; 587 } 588 589 if ($InternalBondStereo) { 590 $BondStereo = SDFileUtil::InternalBondStereochemistryToMDLBondStereo($InternalBondStereo); 591 } 592 593 return $BondStereo; 594 } 595 596 # Zero out charge and radical values specified for atoms... 597 sub _ZeroOutAtomsChargeAndRadicalValues { 598 my($AtomNumToAtomMapRef) = @_; 599 my($Atom); 600 601 for $Atom (values %{$AtomNumToAtomMapRef}) { 602 if ($Atom->HasProperty('FormalCharge')) { 603 $Atom->DeleteProperty('FormalCharge'); 604 } 605 elsif ($Atom->HasProperty('SpinMultiplicity')) { 606 $Atom->DeleteProperty('SpinMultiplicity'); 607 } 608 } 609 } 610 611 # Process charge property value pairs... 612 sub _ProcessChargeProperty { 613 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 614 615 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 616 return; 617 } 618 my($Index, $ValuePairsCount, $AtomNum, $Charge, $Atom); 619 620 $ValuePairsCount = scalar @{$ValuePairsRef}; 621 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 622 $AtomNum = $ValuePairsRef->[$Index]; $Charge = $ValuePairsRef->[$Index + 1]; 623 if (!$Charge) { 624 next VALUEPAIRS; 625 } 626 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 627 next VALUEPAIRS; 628 } 629 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 630 if ($Atom->HasProperty('SpinMultiplicity')) { 631 carp "Warning: ${ClassName}->_ProcessChargeProperty: Setting formal charge on atom number, $AtomNum, with already assigned spin multiplicity value..."; 632 } 633 $Atom->SetFormalCharge($Charge); 634 } 635 } 636 637 # Get charge property value for an atom... 638 sub _GetChargePropertyValue { 639 my($Atom) = @_; 640 my($Charge); 641 642 $Charge = 0; 643 if ($Atom->HasProperty('FormalCharge')) { 644 $Charge = $Atom->GetFormalCharge(); 645 } 646 return $Charge; 647 } 648 649 # Process charge property value pairs... 650 sub _ProcessRadicalProperty { 651 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 652 653 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 654 return; 655 } 656 my($Index, $ValuePairsCount, $AtomNum, $Radical, $SpinMultiplicity, $Atom); 657 658 $ValuePairsCount = scalar @{$ValuePairsRef}; 659 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 660 $AtomNum = $ValuePairsRef->[$Index]; $Radical = $ValuePairsRef->[$Index + 1]; 661 if (!$Radical) { 662 next VALUEPAIRS; 663 } 664 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 665 next VALUEPAIRS; 666 } 667 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 668 if ($Atom->HasProperty('FormalCharge')) { 669 carp "Warning: ${ClassName}->_ProcessRadicalProperty: Setting spin multiplicity on atom number, $AtomNum, with already assigned formal charge value..."; 670 } 671 $SpinMultiplicity = SDFileUtil::MDLRadicalToInternalSpinMultiplicity($Radical); 672 $Atom->SetSpinMultiplicity($SpinMultiplicity); 673 } 674 } 675 676 # Get radical property value for an atom... 677 sub _GetRadicalPropertyValue { 678 my($Atom) = @_; 679 my($Radical, $SpinMultiplicity); 680 681 $Radical = 0; 682 if ($Atom->HasProperty('SpinMultiplicity')) { 683 $SpinMultiplicity = $Atom->GetSpinMultiplicity(); 684 $Radical = SDFileUtil::InternalSpinMultiplicityToMDLRadical($SpinMultiplicity); 685 } 686 return $Radical; 687 } 688 689 # Process isotope property value pairs... 690 sub _ProcessIsotopeProperty { 691 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 692 693 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 694 return; 695 } 696 my($Index, $ValuePairsCount, $AtomNum, $MassNumber, $Atom, $AtomicNumber); 697 698 $ValuePairsCount = scalar @{$ValuePairsRef}; 699 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 700 $AtomNum = $ValuePairsRef->[$Index]; $MassNumber = $ValuePairsRef->[$Index + 1]; 701 if (!$MassNumber) { 702 next VALUEPAIRS; 703 } 704 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 705 next VALUEPAIRS; 706 } 707 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 708 $AtomicNumber = $Atom->GetAtomicNumber(); 709 710 if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $MassNumber)) { 711 my($AtomSymbol) = $Atom->GetAtomSymbol(); 712 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"; 713 } 714 715 # Use SetProperty method instead of SetMassNumber to skip explicit checks on MassNumber value... 716 $Atom->SetProperty('MassNumber', $MassNumber); 717 } 718 } 719 720 # Get isotope property value for an atom... 721 sub _GetIsotopePropertyValue { 722 my($Atom) = @_; 723 my($MassNumber); 724 725 $MassNumber = 0; 726 if ($Atom->HasProperty('MassNumber')) { 727 $MassNumber = $Atom->GetMassNumber(); 728 } 729 return $MassNumber; 730 } 731 732 # Process atom alias property value pairs... 733 sub _ProcessAtomAliasProperty { 734 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 735 736 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 737 return; 738 } 739 my($Index, $ValuePairsCount, $AtomNum, $AtomAlias, $Atom); 740 741 $ValuePairsCount = scalar @{$ValuePairsRef}; 742 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 743 $AtomNum = $ValuePairsRef->[$Index]; $AtomAlias = $ValuePairsRef->[$Index + 1]; 744 if (!$AtomNum) { 745 next VALUEPAIRS; 746 } 747 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 748 next VALUEPAIRS; 749 } 750 $AtomAlias = TextUtil::RemoveLeadingAndTrailingWhiteSpaces($AtomAlias); 751 if (TextUtil::IsEmpty($AtomAlias)) { 752 carp("Warning: ${ClassName}->_ProcessAtomAliasProperty: Ignoring atom alias property line: No Atom alias value specified..."); 753 next VALUEPAIRS; 754 } 755 756 # Set atom symbol to atom alias which sets atomic number automatically... 757 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 758 $Atom->SetAtomSymbol($AtomAlias); 759 760 $Atom->SetProperty('AtomAlias', $AtomAlias); 761 } 762 } 763 764 # Get atom alias property value for an atom... 765 sub _GetAtomAliasPropertyValue { 766 my($Atom) = @_; 767 my($AtomAlias); 768 769 $AtomAlias = undef; 770 if ($Atom->HasProperty('AtomAlias')) { 771 $AtomAlias = $Atom->GetAtomAlias(); 772 } 773 return $AtomAlias; 774 } 775 776 # Is it a MDLMolFileIO object? 777 sub _IsMDLMolFileIO { 778 my($Object) = @_; 779 780 return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0; 781 } 782 783