1 package FileIO::MDLMolFileIO; 2 # 3 # File: MDLMolFileIO.pm 4 # Author: Manish Sud <msud@san.rr.com> 5 # 6 # Copyright (C) 2024 Manish Sud. All rights reserved. 7 # 8 # This file is part of MayaChemTools. 9 # 10 # MayaChemTools is free software; you can redistribute it and/or modify it under 11 # the terms of the GNU Lesser General Public License as published by the Free 12 # Software Foundation; either version 3 of the License, or (at your option) any 13 # later version. 14 # 15 # MayaChemTools is distributed in the hope that it will be useful, but without 16 # any warranty; without even the implied warranty of merchantability of fitness 17 # for a particular purpose. See the GNU Lesser General Public License for more 18 # details. 19 # 20 # You should have received a copy of the GNU Lesser General Public License 21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or 22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, 23 # Boston, MA, 02111-1307, USA. 24 # 25 26 use strict; 27 use Carp; 28 use Exporter; 29 use Scalar::Util (); 30 use TextUtil (); 31 use FileUtil (); 32 use SDFileUtil (); 33 use FileIO::FileIO; 34 use Molecule; 35 36 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 37 38 @ISA = qw(FileIO::FileIO Exporter); 39 @EXPORT = qw(); 40 @EXPORT_OK = qw(IsMDLMolFile); 41 42 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); 43 44 # Setup class variables... 45 my($ClassName); 46 _InitializeClass(); 47 48 # Class constructor... 49 sub new { 50 my($Class, %NamesAndValues) = @_; 51 52 # Initialize object... 53 my $This = $Class->SUPER::new(); 54 bless $This, ref($Class) || $Class; 55 $This->_InitializeMDLMolFileIO(); 56 57 $This->_InitializeMDLMolFileIOProperties(%NamesAndValues); 58 59 return $This; 60 } 61 62 # Initialize any local object data... 63 # 64 sub _InitializeMDLMolFileIO { 65 my($This) = @_; 66 67 # Nothing to do: Base class FileIO handles default class variables... 68 69 return $This; 70 } 71 72 # Initialize class ... 73 sub _InitializeClass { 74 #Class name... 75 $ClassName = __PACKAGE__; 76 77 } 78 79 # Initialize object values... 80 sub _InitializeMDLMolFileIOProperties { 81 my($This, %NamesAndValues) = @_; 82 83 # All other property names and values along with all Set/Get<PropertyName> methods 84 # are implemented on-demand using ObjectProperty class. 85 86 my($Name, $Value, $MethodName); 87 while (($Name, $Value) = each %NamesAndValues) { 88 $MethodName = "Set${Name}"; 89 $This->$MethodName($Value); 90 } 91 92 if (!exists $NamesAndValues{Name}) { 93 croak "Error: ${ClassName}->New: Object can't be instantiated without specifying file name..."; 94 } 95 96 # Make sure it's a MDLMol file... 97 $Name = $NamesAndValues{Name}; 98 if (!$This->IsMDLMolFile($Name)) { 99 croak "Error: ${ClassName}->New: Object can't be instantiated: File, $Name, doesn't appear to be MDLMol format..."; 100 } 101 102 return $This; 103 } 104 105 # Is it a MDLMol file? 106 sub IsMDLMolFile ($;$) { 107 my($FirstParameter, $SecondParameter) = @_; 108 my($This, $FileName, $Status); 109 110 if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) { 111 ($This, $FileName) = ($FirstParameter, $SecondParameter); 112 } 113 else { 114 $FileName = $FirstParameter; 115 } 116 117 # Check file extension... 118 $Status = FileUtil::CheckFileType($FileName, "mol"); 119 120 return $Status; 121 } 122 123 # Read molecule from file and return molecule object... 124 sub ReadMolecule { 125 my($This) = @_; 126 my($FileHandle); 127 128 $FileHandle = $This->GetFileHandle(); 129 return $This->ParseMoleculeString(SDFileUtil::ReadCmpdString($FileHandle)); 130 } 131 132 # Write compound data using Molecule object... 133 sub WriteMolecule { 134 my($This, $Molecule) = @_; 135 136 if (!(defined($Molecule) && $Molecule->IsMolecule())) { 137 carp "Warning: ${ClassName}->WriteMolecule: No data written: Molecule object is not specified..."; 138 return $This; 139 } 140 my($FileHandle); 141 $FileHandle = $This->GetFileHandle(); 142 143 print $FileHandle $This->GenerateMoleculeString($Molecule) . "\n"; 144 145 return $This; 146 } 147 148 # Retrieve molecule string... 149 sub ReadMoleculeString { 150 my($This) = @_; 151 my($FileHandle); 152 153 $FileHandle = $This->GetFileHandle(); 154 return SDFileUtil::ReadCmpdString($FileHandle); 155 } 156 157 # Parse molecule string and return molecule object. ParseMoleculeString supports two invocation methods: class 158 # method or a package function. 159 # 160 sub ParseMoleculeString { 161 my($FirstParameter, $SecondParameter) = @_; 162 my($This, $MoleculeString); 163 164 if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) { 165 ($This, $MoleculeString) = ($FirstParameter, $SecondParameter); 166 } 167 else { 168 $MoleculeString = $FirstParameter; 169 $This = undef; 170 } 171 if (!$MoleculeString) { 172 return undef; 173 } 174 my($LineIndex, @MoleculeLines); 175 @MoleculeLines = split /\n/, $MoleculeString; 176 177 # Create molecule object and set molecule level native and MDL properties... 178 # 179 my($Molecule); 180 $Molecule = new Molecule(); 181 182 # Set valence model for calculating implicit hydrogens... 183 $Molecule->SetValenceModel('MDLValenceModel'); 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, $InternalBondOrder, $InternalBondType, $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 ($InternalBondOrder, $InternalBondType) = SDFileUtil::MDLBondTypeToInternalBondOrder($BondType); 244 $Bond = new Bond('Atoms' => [$Atom1, $Atom2], 'BondOrder' => $InternalBondOrder); 245 $Bond->SetBondType($InternalBondType); 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 A aaa, 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 # 258 my($PropertyLineIndex, $PropertyLine, $FirstChargeOrRadicalLine, @ValuePairs); 259 260 $PropertyLineIndex = $FirstAtomLineIndex + $AtomCount + $BondCount; 261 $PropertyLine = $MoleculeLines[$PropertyLineIndex]; 262 $FirstChargeOrRadicalLine = 1; 263 264 PROPERTYLINE: while ($PropertyLine !~ /^M END/i ) { 265 if ($PropertyLine =~ /\$\$\$\$/) { 266 last PROPERTYLINE; 267 } 268 if ($PropertyLine =~ /^(M CHG|M RAD)/i) { 269 if ($FirstChargeOrRadicalLine) { 270 $FirstChargeOrRadicalLine = 0; 271 _ZeroOutAtomsChargeAndRadicalValues(\%AtomNumToAtomMap); 272 } 273 if ($PropertyLine =~ /^M CHG/i) { 274 @ValuePairs = SDFileUtil::ParseCmpdChargePropertyLine($PropertyLine); 275 _ProcessChargeProperty(\@ValuePairs, \%AtomNumToAtomMap); 276 } 277 elsif ($PropertyLine =~ /^M RAD/i) { 278 @ValuePairs = SDFileUtil::ParseCmpdRadicalPropertyLine($PropertyLine); 279 _ProcessRadicalProperty(\@ValuePairs, \%AtomNumToAtomMap); 280 } 281 } 282 elsif ($PropertyLine =~ /^M ISO/i) { 283 @ValuePairs = SDFileUtil::ParseCmpdIsotopePropertyLine($PropertyLine); 284 _ProcessIsotopeProperty(\@ValuePairs, \%AtomNumToAtomMap); 285 } 286 elsif ($PropertyLine =~ /^A /i) { 287 my($NextPropertyLine); 288 $PropertyLineIndex++; 289 $NextPropertyLine = $MoleculeLines[$PropertyLineIndex]; 290 @ValuePairs = SDFileUtil::ParseCmpdAtomAliasPropertyLine($PropertyLine, $NextPropertyLine); 291 _ProcessAtomAliasProperty(\@ValuePairs, \%AtomNumToAtomMap); 292 } 293 $PropertyLineIndex++; 294 $PropertyLine = $MoleculeLines[$PropertyLineIndex]; 295 } 296 # Store input molecule string as generic property of molecule... 297 $Molecule->SetInputMoleculeString($MoleculeString); 298 299 return $Molecule; 300 } 301 302 # Generate molecule string using molecule object... 303 sub GenerateMoleculeString { 304 my($FirstParameter, $SecondParameter) = @_; 305 my($This, $Molecule); 306 307 if ((@_ == 2) && (_IsMDLMolFileIO($FirstParameter))) { 308 ($This, $Molecule) = ($FirstParameter, $SecondParameter); 309 } 310 else { 311 $Molecule = $FirstParameter; 312 $This = undef; 313 } 314 if (!defined($Molecule)) { 315 return undef; 316 } 317 my(@MoleculeLines); 318 @MoleculeLines = (); 319 320 # First line: Molname line... 321 push @MoleculeLines, SDFileUtil::GenerateCmpdMolNameLine($Molecule->GetName()); 322 323 # Second line: Misc info... 324 my($ProgramName, $UserInitial, $Code); 325 $ProgramName = ''; $UserInitial = ''; $Code = ''; 326 327 $Code = $Molecule->IsThreeDimensional() ? '3D' : '2D'; 328 329 push @MoleculeLines, SDFileUtil::GenerateCmpdMiscInfoLine($ProgramName, $UserInitial, $Code); 330 331 # Third line: Comments line... 332 my($Comments); 333 $Comments = $Molecule->HasProperty('MDLComments') ? $Molecule->GetMDLComments() : ($Molecule->HasProperty('Comments') ? $Molecule->GetComments() : ''); 334 push @MoleculeLines, SDFileUtil::GenerateCmpdCommentsLine($Comments); 335 336 # Fourth line: Counts line for V2000 337 my($AtomCount, $BondCount, $ChiralFlag); 338 $AtomCount = $Molecule->GetNumOfAtoms(); 339 $BondCount = $Molecule->GetNumOfBonds(); 340 $ChiralFlag = 0; 341 push @MoleculeLines, SDFileUtil::GenerateCmpdCountsLine($AtomCount, $BondCount, $ChiralFlag); 342 343 # Atom lines... 344 my($Atom, $AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity, $AtomNum, $AtomID, @Atoms, %AtomIDToNum); 345 my($ChargePropertyValue, $IsotopePropertyValue, $RadicalPropertyValue, $AtomAliasPropertyValue, @IsotopePropertyValuePairs, @ChargePropertyValuePairs, @RadicalPropertyValuePairs, @AtomAliasPropertyValuePairs); 346 347 @ChargePropertyValuePairs = (); 348 @IsotopePropertyValuePairs = (); 349 @RadicalPropertyValuePairs = (); 350 @AtomAliasPropertyValuePairs = (); 351 352 @Atoms = $Molecule->GetAtoms(); 353 354 $AtomNum = 0; 355 for $Atom (@Atoms) { 356 $AtomNum++; 357 $AtomID = $Atom->GetID(); 358 $AtomIDToNum{$AtomID} = $AtomNum; 359 360 $AtomSymbol = $Atom->GetAtomSymbol(); 361 ($AtomX, $AtomY, $AtomZ) = $Atom->GetXYZ(); 362 363 # Setup mass difference... 364 $MassDifference = _GetMassDifference($Atom); 365 if ($MassDifference) { 366 # Hold it for M ISO property lines... 367 $IsotopePropertyValue = _GetIsotopePropertyValue($Atom); 368 if ($IsotopePropertyValue) { 369 push @IsotopePropertyValuePairs, ($AtomNum, $IsotopePropertyValue); 370 } 371 } 372 373 # Setup charge... 374 $Charge = _GetCharge($Atom); 375 if ($Charge) { 376 # Hold it for M CHG property lines... 377 $ChargePropertyValue = _GetChargePropertyValue($Atom); 378 if ($ChargePropertyValue) { 379 push @ChargePropertyValuePairs, ($AtomNum, $ChargePropertyValue); 380 } 381 } 382 383 # Hold any radical values for for M RAD property lines... 384 $RadicalPropertyValue = _GetRadicalPropertyValue($Atom); 385 if ($RadicalPropertyValue) { 386 push @RadicalPropertyValuePairs, ($AtomNum, $RadicalPropertyValue); 387 } 388 389 # Hold any atom alias value for A xxx property lines.... 390 $AtomAliasPropertyValue = _GetAtomAliasPropertyValue($Atom); 391 if ($AtomAliasPropertyValue) { 392 push @AtomAliasPropertyValuePairs, ($AtomNum, $AtomAliasPropertyValue); 393 394 # Set AtomSymbol to carbon as atom alias would override its value during parsing... 395 $AtomSymbol = "C"; 396 } 397 398 # Setup stereo parity... 399 $StereoParity = _GetStereoParity($Atom); 400 401 push @MoleculeLines, SDFileUtil::GenerateCmpdAtomLine($AtomSymbol, $AtomX, $AtomY, $AtomZ, $MassDifference, $Charge, $StereoParity); 402 } 403 404 # Bond lines... 405 my($FirstAtomID, $FirstAtom, $FirstAtomNum, $SecondAtomID, $SecondAtom, $SecondAtomNum, $MDLBondType, $BondOrder, $BondType, $MDLBondStereo, $Bond, @Bonds); 406 for $FirstAtom (@Atoms) { 407 $FirstAtomID = $FirstAtom->GetID(); 408 $FirstAtomNum = $AtomIDToNum{$FirstAtomID}; 409 410 @Bonds = (); 411 @Bonds = $FirstAtom->GetBonds(); 412 BOND: for $Bond (@Bonds) { 413 $SecondAtom = $Bond->GetBondedAtom($FirstAtom); 414 $SecondAtomID = $SecondAtom->GetID(); 415 $SecondAtomNum = $AtomIDToNum{$SecondAtomID}; 416 if ($FirstAtomNum >= $SecondAtomNum) { 417 next BOND; 418 } 419 # Setup BondType... 420 $BondOrder = $Bond->GetBondOrder(); 421 $BondType = $Bond->GetBondType(); 422 $MDLBondType = SDFileUtil::InternalBondOrderToMDLBondType($BondOrder, $BondType); 423 424 # Setup BondStereo... 425 $MDLBondStereo = _GetBondStereo($Bond); 426 427 push @MoleculeLines, SDFileUtil::GenerateCmpdBondLine($FirstAtomNum, $SecondAtomNum, $MDLBondType, $MDLBondStereo); 428 } 429 } 430 # Property lines... 431 if (@IsotopePropertyValuePairs) { 432 push @MoleculeLines, SDFileUtil::GenerateCmpdIsotopePropertyLines(\@IsotopePropertyValuePairs); 433 } 434 if (@ChargePropertyValuePairs) { 435 push @MoleculeLines, SDFileUtil::GenerateCmpdChargePropertyLines(\@ChargePropertyValuePairs); 436 } 437 if (@RadicalPropertyValuePairs) { 438 push @MoleculeLines, SDFileUtil::GenerateCmpdRadicalPropertyLines(\@RadicalPropertyValuePairs); 439 } 440 if (@AtomAliasPropertyValuePairs) { 441 push @MoleculeLines, SDFileUtil::GenerateCmpdAtomAliasPropertyLines(\@AtomAliasPropertyValuePairs); 442 } 443 444 push @MoleculeLines, "M END"; 445 446 return join "\n", @MoleculeLines; 447 } 448 449 # Process MassDifference value and set atom's mass number... 450 # 451 sub _ProcessMassDifference { 452 my($Atom, $MassDifference) = @_; 453 my($MassNumber, $NewMassNumber, $AtomicNumber); 454 455 $AtomicNumber = $Atom->GetAtomicNumber(); 456 457 if (!$AtomicNumber) { 458 carp "Warning: ${ClassName}->_ProcessMassDifference: Ignoring specified mass difference value, $MassDifference, in SD file: Assigned to non standard element..."; 459 return; 460 } 461 $MassNumber = $Atom->GetMassNumber(); 462 if (!$MassDifference) { 463 carp "Warning: ${ClassName}->_ProcessMassDifference: Ignoring specified mass difference value, $MassDifference, in SD file: Unknown MassNumber value..."; 464 return; 465 } 466 $NewMassNumber = $MassNumber + $MassDifference; 467 if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $NewMassNumber)) { 468 my($AtomSymbol) = $Atom->GetAtomSymbol(); 469 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"; 470 } 471 472 # Use SetProperty method instead of SetMassNumber to skip explicit checks on MassNumber value... 473 $Atom->SetProperty('MassNumber', $NewMassNumber); 474 } 475 476 # Get mass difference value... 477 sub _GetMassDifference { 478 my($Atom) = @_; 479 my($MassDifference, $MassNumber, $MostAbundantMassNumber, $AtomicNumber); 480 481 $MassDifference = 0; 482 $MassNumber = $Atom->GetMassNumber(); 483 if (defined $MassNumber) { 484 $AtomicNumber = $Atom->GetAtomicNumber(); 485 if (defined $AtomicNumber) { 486 $MostAbundantMassNumber = PeriodicTable::GetElementMostAbundantNaturalIsotopeMassNumber($AtomicNumber); 487 if (defined($MostAbundantMassNumber) && $MassNumber != $MostAbundantMassNumber) { 488 $MassDifference = $MassNumber - $MostAbundantMassNumber; 489 } 490 } 491 } 492 return $MassDifference; 493 } 494 495 # Process formal charge value and assign it to atom as formal charge... 496 sub _ProcessCharge { 497 my($Atom, $Charge) = @_; 498 my($InternalCharge); 499 500 $InternalCharge = SDFileUtil::MDLChargeToInternalCharge($Charge); 501 $Atom->SetFormalCharge($InternalCharge); 502 } 503 504 # Get MDL formal charge value ... 505 sub _GetCharge { 506 my($Atom) = @_; 507 my($InternalCharge, $Charge); 508 509 $Charge = 0; 510 if ($Atom->HasProperty('FormalCharge')) { 511 $InternalCharge = $Atom->GetFormalCharge(); 512 if ($InternalCharge) { 513 $Charge = SDFileUtil::InternalChargeToMDLCharge($InternalCharge); 514 } 515 } 516 return $Charge; 517 } 518 519 # Process stereo parity value and assign it to atom as MDL property... 520 # 521 # Notes: 522 # . Mark atom as chiral center 523 # . Assign any explicit Clockwise (parity 1), CounterClockwise (parity 2) or either value (parity 3) as property of atom. 524 # . MDL values of Clockwise and CounterClockwise don't correspond to priority assigned to ligands around 525 # stereo center using CIP scheme; consequently, these values can't be used to set internal Stereochemistry for 526 # an atom. 527 # 528 sub _ProcessStereoParity { 529 my($Atom, $StereoParity) = @_; 530 531 $Atom->SetStereoCenter('1'); 532 $Atom->SetMDLStereoParity($StereoParity); 533 } 534 535 # Set stereo parity value to zero for now: The current release of MayaChemTools hasn't implemented 536 # functionality to determine chirality. 537 # 538 sub _GetStereoParity { 539 my($Atom) = @_; 540 my($StereoParity); 541 542 $StereoParity = 0; 543 544 return $StereoParity; 545 } 546 547 # Process bond stereo value... 548 sub _ProcessBondStereo { 549 my($Bond, $BondStereo) = @_; 550 my($InternalBondStereo); 551 552 $InternalBondStereo = SDFileUtil::MDLBondStereoToInternalBondStereochemistry($BondStereo); 553 if ($InternalBondStereo) { 554 $Bond->SetBondStereochemistry($InternalBondStereo); 555 } 556 } 557 558 # Get MDLBondStereo value... 559 sub _GetBondStereo { 560 my($Bond) = @_; 561 my($InternalBondStereo, $BondStereo); 562 563 $BondStereo = 0; 564 565 $InternalBondStereo = ''; 566 BONDSTEREO: { 567 if ($Bond->IsUp()) { 568 $InternalBondStereo = 'Up'; 569 last BONDSTEREO; 570 } 571 if ($Bond->IsDown()) { 572 $InternalBondStereo = 'Down'; 573 last BONDSTEREO; 574 } 575 if ($Bond->IsUpOrDown()) { 576 $InternalBondStereo = 'UpOrDown'; 577 last BONDSTEREO; 578 } 579 if ($Bond->IsCisOrTrans() || $Bond->IsCis() || $Bond->IsTrans()) { 580 $InternalBondStereo = 'CisOrTrans'; 581 last BONDSTEREO; 582 } 583 $InternalBondStereo = ''; 584 } 585 586 if ($InternalBondStereo) { 587 $BondStereo = SDFileUtil::InternalBondStereochemistryToMDLBondStereo($InternalBondStereo); 588 } 589 590 return $BondStereo; 591 } 592 593 # Zero out charge and radical values specified for atoms... 594 sub _ZeroOutAtomsChargeAndRadicalValues { 595 my($AtomNumToAtomMapRef) = @_; 596 my($Atom); 597 598 for $Atom (values %{$AtomNumToAtomMapRef}) { 599 if ($Atom->HasProperty('FormalCharge')) { 600 $Atom->DeleteProperty('FormalCharge'); 601 } 602 elsif ($Atom->HasProperty('SpinMultiplicity')) { 603 $Atom->DeleteProperty('SpinMultiplicity'); 604 } 605 } 606 } 607 608 # Process charge property value pairs... 609 sub _ProcessChargeProperty { 610 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 611 612 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 613 return; 614 } 615 my($Index, $ValuePairsCount, $AtomNum, $Charge, $Atom); 616 617 $ValuePairsCount = scalar @{$ValuePairsRef}; 618 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 619 $AtomNum = $ValuePairsRef->[$Index]; $Charge = $ValuePairsRef->[$Index + 1]; 620 if (!$Charge) { 621 next VALUEPAIRS; 622 } 623 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 624 next VALUEPAIRS; 625 } 626 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 627 if ($Atom->HasProperty('SpinMultiplicity')) { 628 carp "Warning: ${ClassName}->_ProcessChargeProperty: Setting formal charge on atom number, $AtomNum, with already assigned spin multiplicity value..."; 629 } 630 $Atom->SetFormalCharge($Charge); 631 } 632 } 633 634 # Get charge property value for an atom... 635 sub _GetChargePropertyValue { 636 my($Atom) = @_; 637 my($Charge); 638 639 $Charge = 0; 640 if ($Atom->HasProperty('FormalCharge')) { 641 $Charge = $Atom->GetFormalCharge(); 642 } 643 return $Charge; 644 } 645 646 # Process charge property value pairs... 647 sub _ProcessRadicalProperty { 648 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 649 650 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 651 return; 652 } 653 my($Index, $ValuePairsCount, $AtomNum, $Radical, $SpinMultiplicity, $Atom); 654 655 $ValuePairsCount = scalar @{$ValuePairsRef}; 656 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 657 $AtomNum = $ValuePairsRef->[$Index]; $Radical = $ValuePairsRef->[$Index + 1]; 658 if (!$Radical) { 659 next VALUEPAIRS; 660 } 661 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 662 next VALUEPAIRS; 663 } 664 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 665 if ($Atom->HasProperty('FormalCharge')) { 666 carp "Warning: ${ClassName}->_ProcessRadicalProperty: Setting spin multiplicity on atom number, $AtomNum, with already assigned formal charge value..."; 667 } 668 $SpinMultiplicity = SDFileUtil::MDLRadicalToInternalSpinMultiplicity($Radical); 669 $Atom->SetSpinMultiplicity($SpinMultiplicity); 670 } 671 } 672 673 # Get radical property value for an atom... 674 sub _GetRadicalPropertyValue { 675 my($Atom) = @_; 676 my($Radical, $SpinMultiplicity); 677 678 $Radical = 0; 679 if ($Atom->HasProperty('SpinMultiplicity')) { 680 $SpinMultiplicity = $Atom->GetSpinMultiplicity(); 681 $Radical = SDFileUtil::InternalSpinMultiplicityToMDLRadical($SpinMultiplicity); 682 } 683 return $Radical; 684 } 685 686 # Process isotope property value pairs... 687 sub _ProcessIsotopeProperty { 688 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 689 690 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 691 return; 692 } 693 my($Index, $ValuePairsCount, $AtomNum, $MassNumber, $Atom, $AtomicNumber); 694 695 $ValuePairsCount = scalar @{$ValuePairsRef}; 696 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 697 $AtomNum = $ValuePairsRef->[$Index]; $MassNumber = $ValuePairsRef->[$Index + 1]; 698 if (!$MassNumber) { 699 next VALUEPAIRS; 700 } 701 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 702 next VALUEPAIRS; 703 } 704 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 705 $AtomicNumber = $Atom->GetAtomicNumber(); 706 707 if (!PeriodicTable::IsElementNaturalIsotopeMassNumber($AtomicNumber, $MassNumber)) { 708 my($AtomSymbol) = $Atom->GetAtomSymbol(); 709 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"; 710 } 711 712 # Use SetProperty method instead of SetMassNumber to skip explicit checks on MassNumber value... 713 $Atom->SetProperty('MassNumber', $MassNumber); 714 } 715 } 716 717 # Get isotope property value for an atom... 718 sub _GetIsotopePropertyValue { 719 my($Atom) = @_; 720 my($MassNumber); 721 722 $MassNumber = 0; 723 if ($Atom->HasProperty('MassNumber')) { 724 $MassNumber = $Atom->GetMassNumber(); 725 } 726 return $MassNumber; 727 } 728 729 # Process atom alias property value pairs... 730 sub _ProcessAtomAliasProperty { 731 my($ValuePairsRef, $AtomNumToAtomMapRef) = @_; 732 733 if (!(defined($ValuePairsRef) && @{$ValuePairsRef})) { 734 return; 735 } 736 my($Index, $ValuePairsCount, $AtomNum, $AtomAlias, $Atom); 737 738 $ValuePairsCount = scalar @{$ValuePairsRef}; 739 VALUEPAIRS: for ($Index = 0; $Index < $ValuePairsCount; $Index +=2) { 740 $AtomNum = $ValuePairsRef->[$Index]; $AtomAlias = $ValuePairsRef->[$Index + 1]; 741 if (!$AtomNum) { 742 next VALUEPAIRS; 743 } 744 if (!exists $AtomNumToAtomMapRef->{$AtomNum}) { 745 next VALUEPAIRS; 746 } 747 $AtomAlias = TextUtil::RemoveLeadingAndTrailingWhiteSpaces($AtomAlias); 748 if (TextUtil::IsEmpty($AtomAlias)) { 749 carp("Warning: ${ClassName}->_ProcessAtomAliasProperty: Ignoring atom alias property line: No Atom alias value specified..."); 750 next VALUEPAIRS; 751 } 752 753 # Set atom symbol to atom alias which sets atomic number automatically... 754 $Atom = $AtomNumToAtomMapRef->{$AtomNum}; 755 $Atom->SetAtomSymbol($AtomAlias); 756 757 $Atom->SetProperty('AtomAlias', $AtomAlias); 758 } 759 } 760 761 # Get atom alias property value for an atom... 762 sub _GetAtomAliasPropertyValue { 763 my($Atom) = @_; 764 my($AtomAlias); 765 766 $AtomAlias = undef; 767 if ($Atom->HasProperty('AtomAlias')) { 768 $AtomAlias = $Atom->GetAtomAlias(); 769 } 770 return $AtomAlias; 771 } 772 773 # Is it a MDLMolFileIO object? 774 sub _IsMDLMolFileIO { 775 my($Object) = @_; 776 777 return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0; 778 } 779 780