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