1 package Molecule; 2 # 3 # $RCSfile: Molecule.pm,v $ 4 # $Date: 2015/02/28 20:47:18 $ 5 # $Revision: 1.59 $ 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 Storable (); 33 use Scalar::Util (); 34 use ObjectProperty; 35 use MathUtil; 36 use PeriodicTable; 37 use Text::ParseWords; 38 use TextUtil; 39 use FileUtil; 40 use Graph; 41 use Atom; 42 use Bond; 43 use MolecularFormula; 44 45 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 46 47 @ISA = qw(Graph ObjectProperty Exporter); 48 @EXPORT = qw(IsMolecule); 49 @EXPORT_OK = qw(FormatElementalCompositionInformation GetSupportedAromaticityModels IsSupportedAromaticityModel); 50 51 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]); 52 53 # Setup class variables... 54 my($ClassName, $ObjectID, %AromaticityModelsDataMap, %CanonicalAromaticityModelNamesMap); 55 _InitializeClass(); 56 57 # Overload Perl functions... 58 use overload '""' => 'StringifyMolecule'; 59 60 # Class constructor... 61 sub new { 62 my($Class, %NamesAndValues) = @_; 63 64 # Initialize object... 65 my $This = $Class->SUPER::new(); 66 bless $This, ref($Class) || $Class; 67 $This->_InitializeMolecule(); 68 69 if (keys %NamesAndValues) { $This->_InitializeMoleculeProperties(%NamesAndValues); } 70 71 return $This; 72 } 73 74 # Initialize object data... 75 sub _InitializeMolecule { 76 my($This) = @_; 77 my($ObjectID) = _GetNewObjectID(); 78 79 # All other property names and values along with all Set/Get<PropertyName> methods 80 # are implemented on-demand using ObjectProperty class. 81 $This->{ID} = $ObjectID; 82 $This->{Name} = "Molecule ${ObjectID}"; 83 } 84 85 # Initialize molecule properties... 86 sub _InitializeMoleculeProperties { 87 my($This, %NamesAndValues) = @_; 88 89 my($Name, $Value, $MethodName); 90 while (($Name, $Value) = each %NamesAndValues) { 91 $MethodName = "Set${Name}"; 92 $This->$MethodName($Value); 93 } 94 } 95 96 # Initialize class ... 97 sub _InitializeClass { 98 #Class name... 99 $ClassName = __PACKAGE__; 100 101 # ID to keep track of objects... 102 $ObjectID = 0; 103 104 # Load molecule class data... 105 _LoadMoleculeClassData(); 106 } 107 108 # Setup an explicit SetID method to block setting of ID by AUTOLOAD function... 109 sub SetID { 110 my($This, $Value) = @_; 111 112 carp "Warning: ${ClassName}->SetID: Object ID can't be changed: it's used for internal tracking..."; 113 114 return $This; 115 } 116 117 # Add an atom... 118 sub AddAtom { 119 my($This, $Atom) = @_; 120 121 if (!defined $Atom) { 122 carp "Warning: ${ClassName}->AddAtom: No atom added: Atom must be specified..."; 123 return undef; 124 } 125 if ($This->HasAtom($Atom)) { 126 carp "Warning: ${ClassName}->AddAtom: No atom added: Atom already exists..."; 127 return undef; 128 } 129 return $This->_AddAtom($Atom); 130 } 131 132 # Add an atom... 133 sub _AddAtom { 134 my($This, $Atom) = @_; 135 136 # Assign atom to this molecule... 137 $Atom->_SetMolecule($This); 138 139 # Add it to the graph as a vertex... 140 my($AtomID); 141 $AtomID = $Atom->GetID(); 142 $This->AddVertex($AtomID); 143 $This->SetVertexProperty('Atom', $Atom, $AtomID); 144 145 return $This; 146 } 147 148 # Add atoms... 149 sub AddAtoms { 150 my($This, @Atoms) = @_; 151 152 if (!@Atoms) { 153 carp "Warning: ${ClassName}->AddAtoms: No atoms added: Atoms list must be specified..."; 154 return undef; 155 } 156 my($Atom); 157 for $Atom (@Atoms) { 158 $This->AddAtom($Atom); 159 } 160 return $This; 161 } 162 163 # Create an atom and add it to molecule... 164 sub NewAtom { 165 my($This, %NamesAndValues) = @_; 166 my($Atom); 167 168 $Atom = new Atom(%NamesAndValues); 169 $This->AddAtom($Atom); 170 171 return $Atom; 172 } 173 174 # Delete an atom... 175 sub DeleteAtom { 176 my($This, $Atom) = @_; 177 178 if (!defined $Atom) { 179 carp "Warning: ${ClassName}->DeleteAtom: No atom deleted: Atom must be specified..."; 180 return undef; 181 } 182 # Does the atom exist in molecule? 183 if (!$This->HasAtom($Atom)) { 184 carp "Warning: ${ClassName}->DeleteAtom: No atom deleted: Atom doesn't exist..."; 185 return undef; 186 } 187 return $This->_DeleteAtom($Atom); 188 } 189 190 # Delete atom... 191 sub _DeleteAtom { 192 my($This, $Atom) = @_; 193 194 my($AtomID); 195 $AtomID = $Atom->GetID(); 196 $This->DeleteVertex($AtomID); 197 198 return $This; 199 } 200 201 # Delete atoms... 202 sub DeleteAtoms { 203 my($This, @Atoms) = @_; 204 205 if (!@Atoms) { 206 carp "Warning: ${ClassName}->DeleteAtoms: No atoms added: Atoms list must be specified..."; 207 return undef; 208 } 209 my($Atom); 210 for $Atom (@Atoms) { 211 $This->DeleteAtom($Atom); 212 } 213 214 return $This; 215 } 216 217 # Is this atom present? 218 sub HasAtom { 219 my($This, $Atom) = @_; 220 221 if (!defined $Atom) { 222 return 0; 223 } 224 if (!$Atom->HasProperty('Molecule')) { 225 # It's not in molecule... 226 return 0; 227 } 228 my($AtomID); 229 $AtomID = $Atom->GetID(); 230 if (!$This->HasVertex($AtomID)) { 231 # It's not in molecule... 232 return 0; 233 } 234 my($Molecule); 235 $Molecule = $Atom->GetProperty('Molecule'); 236 237 return ($This->HasVertex($AtomID) && $This->GetID() == $Molecule->GetID()) ? 1 : 0; 238 } 239 240 # Return an array of atoms. In scalar context, it returns number of atoms. Additionally, 241 # atoms array can be filtered by any user specifiable Atom class method... 242 # 243 sub GetAtoms { 244 my($This, $AtomCheckMethodName, $NegateMethodResult) = @_; 245 my(@Atoms, @AtomIDs); 246 247 @Atoms = (); @AtomIDs = (); 248 249 @AtomIDs = $This->GetVertices(); 250 if (!@AtomIDs) { 251 return wantarray ? @Atoms : scalar @Atoms; 252 } 253 254 @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs); 255 256 if (!defined $AtomCheckMethodName) { 257 return wantarray ? @Atoms : scalar @Atoms; 258 } 259 $NegateMethodResult = (defined($NegateMethodResult) && $NegateMethodResult) ? 1 : 0; 260 261 # Filter out atoms... 262 my($Atom, $KeepAtom, @FilteredAtoms); 263 @FilteredAtoms = (); 264 for $Atom (@Atoms) { 265 $KeepAtom = $NegateMethodResult ? (!$Atom->$AtomCheckMethodName()) : $Atom->$AtomCheckMethodName(); 266 if ($KeepAtom) { 267 push @FilteredAtoms, $Atom; 268 } 269 } 270 return wantarray ? @FilteredAtoms : scalar @FilteredAtoms; 271 } 272 273 # Return an array of bonds. In scalar context, it returns number of bonds... 274 sub GetBonds { 275 my($This) = @_; 276 my(@Bonds, @EdgesAtomsIDs); 277 278 @Bonds = (); @EdgesAtomsIDs = (); 279 280 @EdgesAtomsIDs = $This->GetEdges(); 281 if (@EdgesAtomsIDs) { 282 @Bonds = $This->GetEdgesProperty('Bond', @EdgesAtomsIDs); 283 } 284 return wantarray ? @Bonds : scalar @Bonds; 285 } 286 287 # Get number of atoms in molecule... 288 sub GetNumOfAtoms { 289 my($This) = @_; 290 my($NumOfAtoms); 291 292 $NumOfAtoms = $This->GetAtoms(); 293 294 return $NumOfAtoms; 295 } 296 297 # Get number of bonds in molecule... 298 sub GetNumOfBonds { 299 my($This) = @_; 300 my($NumOfBonds); 301 302 $NumOfBonds = $This->GetBonds(); 303 304 return $NumOfBonds; 305 } 306 307 # Get number of heavy atoms in molecule... 308 sub GetNumOfHeavyAtoms { 309 my($This) = @_; 310 311 return $This->GetNumOfNonHydrogenAtoms(); 312 } 313 314 # Get number of non-hydrogen atoms in molecule... 315 sub GetNumOfNonHydrogenAtoms { 316 my($This) = @_; 317 my($NumOfNonHydrogenAtoms, $Atom, @Atoms); 318 319 @Atoms = $This->GetAtoms(); 320 $NumOfNonHydrogenAtoms = 0; 321 for $Atom (@Atoms) { 322 if (!$Atom->IsHydrogen()) { 323 $NumOfNonHydrogenAtoms++; 324 } 325 } 326 return $NumOfNonHydrogenAtoms; 327 } 328 329 # Get number of hydrogen atoms in molecule... 330 sub GetNumOfHydrogenAtoms { 331 my($This) = @_; 332 my($NumOfHydrogenAtoms, $Atom, @Atoms); 333 334 @Atoms = $This->GetAtoms(); 335 $NumOfHydrogenAtoms = 0; 336 for $Atom (@Atoms) { 337 if ($Atom->IsHydrogen()) { 338 $NumOfHydrogenAtoms++; 339 } 340 } 341 return $NumOfHydrogenAtoms; 342 } 343 344 # Get number of missing hydrogen atoms in molecule... 345 sub GetNumOfMissingHydrogenAtoms { 346 my($This) = @_; 347 my($NumOfMissingHydrogenAtoms, $Atom, @Atoms); 348 349 @Atoms = $This->GetAtoms(); 350 $NumOfMissingHydrogenAtoms = 0; 351 for $Atom (@Atoms) { 352 if (!$Atom->IsHydrogen()) { 353 $NumOfMissingHydrogenAtoms += $Atom->GetNumOfMissingHydrogens(); 354 } 355 } 356 return $NumOfMissingHydrogenAtoms; 357 } 358 359 # Add bond... 360 sub AddBond { 361 my($This, $Bond) = @_; 362 my($Atom1, $Atom2); 363 364 ($Atom1, $Atom2) = $Bond->GetAtoms(); 365 if (!(defined($Atom1) && defined($Atom2))) { 366 carp "Warning: ${ClassName}->AddBond: No bond added: Both atoms must be specified..."; 367 return undef; 368 } 369 if (!($This->HasAtom($Atom1) && $This->HasAtom($Atom2))) { 370 carp "Warning: ${ClassName}->AddBond: No bond added: Both atoms must be present..."; 371 return undef; 372 } 373 if ($This->HasBond($Bond)) { 374 carp "Warning: ${ClassName}->AddBond: No bond added: Bond already exists..."; 375 return undef; 376 } 377 return $This->_AddBond($Bond); 378 } 379 380 # Add bond... 381 sub _AddBond { 382 my($This, $Bond) = @_; 383 384 # Assign bond to this molecule... 385 $Bond->_SetMolecule($This); 386 387 # Add it to the graph as an edge... 388 my($Atom1, $Atom2, $AtomID1, $AtomID2); 389 ($Atom1, $Atom2) = $Bond->GetAtoms(); 390 $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); 391 $This->AddEdge($AtomID1, $AtomID2); 392 $This->SetEdgeProperty('Bond', $Bond, $AtomID1, $AtomID2); 393 394 return $This; 395 } 396 397 # Add Bonds... 398 sub AddBonds { 399 my($This, @Bonds) = @_; 400 401 if (!@Bonds) { 402 carp "Warning: ${ClassName}->AddBonds: No bonds added: Bonds list must be specified..."; 403 return undef; 404 } 405 my($Bond); 406 for $Bond (@Bonds) { 407 $This->AddBond($Bond); 408 } 409 return $This; 410 } 411 412 # Create a bond and add it to molecule... 413 sub NewBond { 414 my($This, %NamesAndValues) = @_; 415 my($Bond); 416 417 $Bond = new Bond(%NamesAndValues); 418 $This->AddBond($Bond); 419 420 return $Bond; 421 } 422 423 # Delete a bond... 424 sub DeleteBond { 425 my($This, $Bond) = @_; 426 427 if (!defined $Bond) { 428 carp "Warning: ${ClassName}->DeleteBond: No bond deleted: Bond must be specified..."; 429 return undef; 430 } 431 # Does the bond exist in molecule? 432 if (!$This->HasBond($Bond)) { 433 carp "Warning: ${ClassName}->DeleteBond: No bond deleted: Bond doesn't exist..."; 434 return undef; 435 } 436 return $This->_DeleteBond($Bond); 437 } 438 439 # Delete bond... 440 sub _DeleteBond { 441 my($This, $Bond) = @_; 442 443 my($Atom1, $Atom2, $AtomID1, $AtomID2); 444 ($Atom1, $Atom2) = $Bond->GetAtoms(); 445 $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); 446 $This->DeleteEdge($AtomID1, $AtomID2); 447 448 return $This; 449 } 450 451 # Delete bonds... 452 sub DeleteBonds { 453 my($This, @Bonds) = @_; 454 455 if (!@Bonds) { 456 carp "Warning: ${ClassName}->DeleteBonds: No bonds added: Bonds list must be specified..."; 457 return undef; 458 } 459 my($Bond); 460 for $Bond (@Bonds) { 461 $This->DeleteBond($Bond); 462 } 463 464 return $This; 465 } 466 467 # Has bond... 468 sub HasBond { 469 my($This, $Bond) = @_; 470 my($Atom1, $Atom2); 471 472 ($Atom1, $Atom2) = $Bond->GetAtoms(); 473 if (!($This->HasAtom($Atom1) && $This->HasAtom($Atom2))) { 474 return 0; 475 } 476 if (!$Bond->HasProperty('Molecule')) { 477 # It's not in molecule... 478 return 0; 479 } 480 my($AtomID1, $AtomID2, $Molecule); 481 $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); 482 $Molecule = $Bond->GetMolecule(); 483 484 return ($This->HasEdge($AtomID1, $AtomID2) && $This->GetID() == $Molecule->GetID()) ? 1 : 0; 485 } 486 487 # Get atom neighbors... 488 sub _GetAtomNeighbors { 489 my($This, $Atom) = @_; 490 491 my($AtomID, @Atoms, @AtomIDs); 492 493 @Atoms = (); @AtomIDs = (); 494 $AtomID = $Atom->GetID(); 495 @AtomIDs = $This->GetNeighbors($AtomID); 496 if (@AtomIDs) { 497 @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs); 498 } 499 return wantarray ? @Atoms : scalar @Atoms; 500 } 501 502 # Get atom bonds... 503 sub _GetAtomBonds { 504 my($This, $Atom) = @_; 505 my($AtomID, @AtomIDs, @Bonds); 506 507 @Bonds = (); @AtomIDs = (); 508 $AtomID = $Atom->GetID(); 509 @AtomIDs = $This->GetEdges($AtomID); 510 if (@AtomIDs) { 511 @Bonds = $This->GetEdgesProperty('Bond', @AtomIDs); 512 } 513 return wantarray ? @Bonds : scalar @Bonds; 514 } 515 516 # Get bond to specified atom... 517 sub _GetBondToAtom { 518 my($This, $Atom1, $Atom2) = @_; 519 my($AtomID1, $AtomID2); 520 521 $AtomID1 = $Atom1->GetID(); 522 $AtomID2 = $Atom2->GetID(); 523 524 return $This->GetEdgeProperty('Bond', $AtomID1, $AtomID2); 525 } 526 527 # Are two specified atoms bonded? 528 sub _IsBondedToAtom { 529 my($This, $Atom1, $Atom2) = @_; 530 my($AtomID1, $AtomID2); 531 532 $AtomID1 = $Atom1->GetID(); 533 $AtomID2 = $Atom2->GetID(); 534 535 return $This->HasEdgeProperty('Bond', $AtomID1, $AtomID2); 536 } 537 538 # Add hydrogens to each atoms in molecule and return total number of hydrogens added... 539 sub AddHydrogens { 540 my($This) = @_; 541 542 return $This->_AddHydrogens(); 543 } 544 545 # Add hydrogens to polar atoms (N, O, P, S) in molecule and return total number of hydrogens added... 546 sub AddPolarHydrogens { 547 my($This) = @_; 548 my($PolarHydrogensOnly) = 1; 549 550 return $This->_AddHydrogens($PolarHydrogensOnly); 551 } 552 553 # Add all the hydrogens or hydrogens for polar atoms only... 554 # 555 # Note: 556 # . The current release of MayaChemTools doesn't assign any hydrogen positions. 557 # 558 sub _AddHydrogens { 559 my($This, $PolarHydrogensOnly) = @_; 560 my($Atom, $NumOfHydrogensAdded, $HydrogenPositionsWarning, @Atoms); 561 562 if (! defined $PolarHydrogensOnly) { 563 $PolarHydrogensOnly = 0; 564 } 565 566 $NumOfHydrogensAdded = 0; 567 @Atoms = $This->GetAtoms(); 568 $HydrogenPositionsWarning = 0; 569 570 ATOM: for $Atom (@Atoms) { 571 if ($PolarHydrogensOnly) { 572 if (!$Atom->IsPolarAtom()) { 573 next ATOM; 574 } 575 } 576 $NumOfHydrogensAdded += $Atom->AddHydrogens($HydrogenPositionsWarning); 577 } 578 return $NumOfHydrogensAdded; 579 } 580 581 # Delete all hydrogens atoms in molecule and return total number of hydrogens removed... 582 sub DeleteHydrogens { 583 my($This) = @_; 584 585 return $This->_DeleteHydrogens(); 586 } 587 588 # Delete hydrogens to polar atoms (N, O, P, S) in molecule and return total number of hydrogens removed... 589 sub DeletePolarHydrogens { 590 my($This) = @_; 591 my($PolarHydrogensOnly) = 1; 592 593 return $This->_DeleteHydrogens($PolarHydrogensOnly); 594 } 595 596 # Delete all hydrogens atoms in molecule and return total number of hydrogens removed... 597 sub _DeleteHydrogens { 598 my($This, $PolarHydrogensOnly) = @_; 599 my($Atom, $NumOfHydrogensRemoved, @Atoms); 600 601 if (! defined $PolarHydrogensOnly) { 602 $PolarHydrogensOnly = 0; 603 } 604 605 $NumOfHydrogensRemoved = 0; 606 @Atoms = $This->GetAtoms(); 607 608 ATOM: for $Atom (@Atoms) { 609 if ($PolarHydrogensOnly) { 610 if (!$Atom->IsPolarHydrogen()) { 611 next ATOM; 612 } 613 } 614 elsif (!$Atom->IsHydrogen()) { 615 next ATOM; 616 } 617 $This->_DeleteAtom($Atom); 618 $NumOfHydrogensRemoved++; 619 } 620 return $NumOfHydrogensRemoved; 621 } 622 623 # Get molecular weight by summing up atomic weights of all the atoms... 624 sub GetMolecularWeight { 625 my($This, $IncludeMissingHydrogens) = @_; 626 my($MolecularWeight, $AtomicWeight, @Atoms, $Atom); 627 628 $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; 629 630 $MolecularWeight = 0; 631 @Atoms = $This->GetAtoms(); 632 for $Atom (@Atoms) { 633 $AtomicWeight = $Atom->GetAtomicWeight(); 634 if (defined $AtomicWeight) { 635 $MolecularWeight += $AtomicWeight; 636 } 637 } 638 639 if (!$IncludeMissingHydrogens) { 640 return $MolecularWeight; 641 } 642 643 # Account for missing hydrogen atoms... 644 my($NumOfMissingHydrogenAtoms); 645 646 $NumOfMissingHydrogenAtoms = $This->GetNumOfMissingHydrogenAtoms(); 647 if ($NumOfMissingHydrogenAtoms) { 648 $MolecularWeight += $NumOfMissingHydrogenAtoms * PeriodicTable::GetElementAtomicWeight('H'); 649 } 650 651 return $MolecularWeight; 652 } 653 654 # Get exact mass by summing up exact masses of all the atoms... 655 sub GetExactMass { 656 my($This, $IncludeMissingHydrogens) = @_; 657 my($ExactMass, $AtomicMass, @Atoms, $Atom); 658 659 $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; 660 661 $ExactMass = 0; 662 @Atoms = $This->GetAtoms(); 663 for $Atom (@Atoms) { 664 $AtomicMass = $Atom->GetExactMass(); 665 if (defined $AtomicMass) { 666 $ExactMass += $AtomicMass; 667 } 668 } 669 670 if (!$IncludeMissingHydrogens) { 671 return $ExactMass; 672 } 673 674 # Account for missing hydrogen atoms... 675 my($NumOfMissingHydrogenAtoms); 676 677 $NumOfMissingHydrogenAtoms = $This->GetNumOfMissingHydrogenAtoms(); 678 if ($NumOfMissingHydrogenAtoms) { 679 $ExactMass += $NumOfMissingHydrogenAtoms * PeriodicTable::GetElementMostAbundantNaturalIsotopeMass('H'); 680 } 681 682 return $ExactMass; 683 } 684 685 # Get net formal charge on the molecule using one of the following two methods: 686 # . Using explicitly set FormalCharge property 687 # . Adding up formal charge on each atom in the molecule 688 # 689 # Caveats: 690 # . FormalCharge is different from Charge property of the molecule which corresponds to 691 # sum of partial atomic charges explicitly set for each atom using a specific methodology. 692 # 693 sub GetFormalCharge { 694 my($This) = @_; 695 696 # Is FormalCharge property explicitly set? 697 if ($This->HasProperty('FormalCharge')) { 698 return $This->GetProperty('FormalCharge'); 699 } 700 my($FormalCharge, $AtomicFormalCharge, @Atoms, $Atom); 701 702 $FormalCharge = 0; 703 @Atoms = $This->GetAtoms(); 704 for $Atom (@Atoms) { 705 $AtomicFormalCharge = $Atom->GetFormalCharge(); 706 if (defined $AtomicFormalCharge) { 707 $FormalCharge += $AtomicFormalCharge; 708 } 709 } 710 return $FormalCharge; 711 } 712 713 # Get net charge on the molecule using one of the following two methods: 714 # . Using explicitly set Charge property 715 # . Adding up charge on each atom in the molecule 716 # 717 # Caveats: 718 # . FormalCharge is different from Charge property of the molecule which corresponds to 719 # sum of partial atomic charges explicitly set for each atom using a specific methodology. 720 # 721 sub GetCharge { 722 my($This) = @_; 723 724 # Is Charge property explicitly set? 725 if ($This->HasProperty('Charge')) { 726 return $This->GetProperty('Charge'); 727 } 728 my($Charge, $AtomicCharge, @Atoms, $Atom); 729 730 $Charge = 0; 731 @Atoms = $This->GetAtoms(); 732 for $Atom (@Atoms) { 733 $AtomicCharge = $Atom->GetCharge(); 734 if (defined $AtomicCharge) { 735 $Charge += $AtomicCharge; 736 } 737 } 738 return $Charge; 739 } 740 741 # Get total SpinMultiplicity for the molecule using one of the following two methods: 742 # . Using explicitly set SpinMultiplicity property 743 # . Adding up SpinMultiplicity on each atom in the molecule 744 # 745 # 746 sub GetSpinMultiplicity { 747 my($This) = @_; 748 749 # Is SpinMultiplicity property explicitly set? 750 if ($This->HasProperty('SpinMultiplicity')) { 751 return $This->GetProperty('SpinMultiplicity'); 752 } 753 my($AtomicSpinMultiplicity, $SpinMultiplicity, @Atoms, $Atom); 754 755 $SpinMultiplicity = 0; 756 @Atoms = $This->GetAtoms(); 757 for $Atom (@Atoms) { 758 $AtomicSpinMultiplicity = $Atom->GetSpinMultiplicity(); 759 if (defined $AtomicSpinMultiplicity) { 760 $SpinMultiplicity += $AtomicSpinMultiplicity; 761 } 762 } 763 return $SpinMultiplicity; 764 } 765 766 # Get total FreeRadicalElectrons for the molecule using one of the following two methods: 767 # . Using explicitly set FreeRadicalElectrons property 768 # . Adding up FreeRadicalElectrons on each atom in the molecule 769 # 770 # 771 sub GetFreeRadicalElectrons { 772 my($This) = @_; 773 774 # Is FreeRadicalElectrons property explicitly set? 775 if ($This->HasProperty('FreeRadicalElectrons')) { 776 return $This->GetProperty('FreeRadicalElectrons'); 777 } 778 my($AtomicFreeRadicalElectrons, $FreeRadicalElectrons, @Atoms, $Atom); 779 780 $FreeRadicalElectrons = 0; 781 @Atoms = $This->GetAtoms(); 782 for $Atom (@Atoms) { 783 $AtomicFreeRadicalElectrons = $Atom->GetFreeRadicalElectrons(); 784 if (defined $AtomicFreeRadicalElectrons) { 785 $FreeRadicalElectrons += $AtomicFreeRadicalElectrons; 786 } 787 } 788 return $FreeRadicalElectrons; 789 } 790 791 # Set valence model... 792 # 793 sub SetValenceModel { 794 my($This, $ValenceModel) = @_; 795 796 if ($ValenceModel !~ /^(MDLValenceModel|DaylightValenceModel|InternalValenceModel|MayaChemToolsValenceModel)$/i) { 797 carp "Warning: ${ClassName}->SetValenceModel: The current release of MayaChemTools doesn't support the specified valence model $ValenceModel. Supported valence models: MDLValenceModel, DaylightValenceModel, InternalValenceModel or MayaChemToolsValenceModel. Using internal valence model..."; 798 $ValenceModel = 'InternalValenceModel'; 799 } 800 801 $This->SetProperty('ValenceModel', $ValenceModel); 802 803 return $This; 804 } 805 806 # Get valence model... 807 # 808 sub GetValenceModel { 809 my($This) = @_; 810 811 # Is ValenceModel property explicitly set? 812 if ($This->HasProperty('ValenceModel')) { 813 return $This->GetProperty('ValenceModel'); 814 } 815 816 # Used internal valence model as default model... 817 return 'InternalValenceModel'; 818 } 819 820 # Get molecular formula by collecting information about all atoms in the molecule and 821 # composing the formula using Hills ordering system: 822 # . C shows up first and H follows assuming C is present 823 # . All other standard elements are sorted alphanumerically 824 # . All other non-stanard atom symbols are also sorted alphanumerically and follow standard elements 825 # 826 # Caveats: 827 # . By default, missing hydrogens and nonelements are also included 828 # . Elements for disconnected fragments are combined into the same formula 829 # 830 # Handle formula generation for disconnected structures. e.g: molecule generated by 831 # [Na+].[O-]c1ccccc1 832 # 833 sub GetMolecularFormula { 834 my($This, $IncludeMissingHydrogens, $IncludeNonElements) = @_; 835 my($MolecularFormula, $AtomSymbol, $ElementsCountRef, $NonElementsCountRef); 836 837 $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; 838 $IncludeNonElements = defined($IncludeNonElements) ? $IncludeNonElements : 1; 839 840 # Get elements count and setup molecular formula... 841 ($ElementsCountRef, $NonElementsCountRef) = $This->GetElementsAndNonElements($IncludeMissingHydrogens); 842 $MolecularFormula = ''; 843 844 # Count C and H first... 845 if (exists $ElementsCountRef->{C} ) { 846 $MolecularFormula .= 'C' . ($ElementsCountRef->{C} > 1 ? $ElementsCountRef->{C} : ''); 847 delete $ElementsCountRef->{C}; 848 849 if (exists $ElementsCountRef->{H} ) { 850 $MolecularFormula .= 'H' . ($ElementsCountRef->{H} > 1 ? $ElementsCountRef->{H} : ''); 851 delete $ElementsCountRef->{H}; 852 } 853 } 854 855 # Sort elements... 856 for $AtomSymbol (sort {$a cmp $b} keys %{$ElementsCountRef}) { 857 $MolecularFormula .= $AtomSymbol . ($ElementsCountRef->{$AtomSymbol} > 1 ? $ElementsCountRef->{$AtomSymbol} : ''); 858 } 859 860 # Sort non-elements... 861 if ($IncludeNonElements) { 862 for $AtomSymbol (sort {$a cmp $b} keys %{$NonElementsCountRef}) { 863 $MolecularFormula .= $AtomSymbol . ($NonElementsCountRef->{$AtomSymbol} > 1 ? $NonElementsCountRef->{$AtomSymbol} : ''); 864 } 865 } 866 867 # Check formal charge... 868 my($FormalCharge); 869 $FormalCharge = $This->GetFormalCharge(); 870 if ($FormalCharge) { 871 # Setup formal charge string... 872 my($FormalChargeString); 873 if ($FormalCharge == 1 ) { 874 $FormalChargeString = "+"; 875 } 876 elsif ($FormalCharge == -1 ) { 877 $FormalChargeString = "-"; 878 } 879 else { 880 $FormalChargeString = ($FormalCharge > 0) ? ("+" . abs($FormalCharge)) : ("-" . abs($FormalCharge)); 881 } 882 $MolecularFormula = "${MolecularFormula}${FormalChargeString}"; 883 } 884 885 return $MolecularFormula; 886 } 887 888 # Count elements and non-elements in molecule and return references to hashes 889 # containing count of elements and non-elements respectively. By default, missing 890 # hydrogens are not added to the element hash. 891 # 892 # 893 sub GetElementsAndNonElements { 894 my($This, $IncludeMissingHydrogens) = @_; 895 my($Atom, $AtomicNumber, $AtomSymbol, $NumOfMissingHydrogens, @Atoms, %ElementsCount, %NonElementsCount); 896 897 $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 0; 898 899 %ElementsCount = (); %NonElementsCount = (); 900 $NumOfMissingHydrogens = 0; 901 902 # Count elements and non elements... 903 @Atoms = $This->GetAtoms(); 904 for $Atom (@Atoms) { 905 $AtomicNumber = $Atom->GetAtomicNumber(); 906 $AtomSymbol = $Atom->GetAtomSymbol(); 907 if ($AtomicNumber) { 908 if (exists $ElementsCount{$AtomSymbol}) { 909 $ElementsCount{$AtomSymbol} += 1; 910 } 911 else { 912 $ElementsCount{$AtomSymbol} = 1; 913 } 914 if ($IncludeMissingHydrogens) { 915 $NumOfMissingHydrogens += $Atom->GetNumOfMissingHydrogens(); 916 } 917 } 918 else { 919 if (exists $NonElementsCount{$AtomSymbol}) { 920 $NonElementsCount{$AtomSymbol} += 1; 921 } 922 else { 923 $NonElementsCount{$AtomSymbol} = 1; 924 } 925 } 926 } 927 if ($IncludeMissingHydrogens && $NumOfMissingHydrogens) { 928 $AtomSymbol = 'H'; 929 if (exists $ElementsCount{$AtomSymbol}) { 930 $ElementsCount{$AtomSymbol} += $NumOfMissingHydrogens; 931 } 932 else { 933 $ElementsCount{$AtomSymbol} = $NumOfMissingHydrogens; 934 } 935 } 936 937 return (\%ElementsCount, \%NonElementsCount); 938 } 939 940 # Get number of element and non-elements in molecule. By default, missing 941 # hydrogens are not added to element count. 942 # 943 sub GetNumOfElementsAndNonElements { 944 my($This, $IncludeMissingHydrogens) = @_; 945 my($ElementCount, $NonElementCount, $Atom); 946 947 $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 0; 948 949 ($ElementCount, $NonElementCount) = (0, 0); 950 951 ATOM: for $Atom ($This->GetAtoms()) { 952 if (!$Atom->GetAtomicNumber()) { 953 $NonElementCount++; 954 next ATOM; 955 } 956 # Process element... 957 $ElementCount++; 958 if ($IncludeMissingHydrogens) { 959 if (!$Atom->IsHydrogen()) { 960 $ElementCount += $Atom->GetNumOfMissingHydrogens(); 961 } 962 } 963 } 964 965 return ($ElementCount, $NonElementCount); 966 } 967 968 # Calculate elemental composition and return reference to arrays 969 # containing elements and their percent composition. 970 # 971 # Caveats: 972 # . By default, missing hydrogens are included 973 # . Non elemnents are ignored 974 # . Mass number are ignored 975 # 976 sub GetElementalComposition { 977 my($This, $IncludeMissingHydrogens) = @_; 978 my($MolecularFormula, $IncludeNonElements, $ElementsCountRef, $NonElementsCountRef, $ElementsRef, $ElementsCompositionRef); 979 980 $IncludeMissingHydrogens = defined($IncludeMissingHydrogens) ? $IncludeMissingHydrogens : 1; 981 982 $IncludeNonElements = 0; 983 ($ElementsCountRef, $NonElementsCountRef) = $This->GetElementsAndNonElements($IncludeMissingHydrogens); 984 985 $MolecularFormula = $This->GetMolecularFormula($IncludeMissingHydrogens, $IncludeNonElements); 986 987 ($ElementsRef, $ElementsCompositionRef) = MolecularFormula::CalculateElementalComposition($MolecularFormula); 988 989 return ($ElementsRef, $ElementsCompositionRef); 990 } 991 992 # Using refernece to element and its composition arrays, format composition information 993 # as: Element: Composition;... 994 # 995 sub FormatElementalCompositionInformation { 996 my($FirstParameter, $SecondParameter, $ThirdParameter, $FourthParameter) = @_; 997 my($This, $ElementsRef, $ElementCompositionRef, $Precision); 998 999 if (_IsMolecule($FirstParameter)) { 1000 ($This, $ElementsRef, $ElementCompositionRef, $Precision) = ($FirstParameter, $SecondParameter, $ThirdParameter, $FourthParameter); 1001 } 1002 else { 1003 ($ElementsRef, $ElementCompositionRef, $Precision) = ($FirstParameter, $SecondParameter, $ThirdParameter); 1004 } 1005 my($FormattedInfo) = ''; 1006 1007 if (!(defined($ElementsRef) && @{$ElementsRef})) { 1008 carp "Warning: ${ClassName}->FormatElementalCompositionInformation: Elements list is not defined or empty..."; 1009 return undef; 1010 } 1011 if (!(defined($ElementCompositionRef) && @{$ElementCompositionRef})) { 1012 carp "Warning: ${ClassName}->FormatElementalCompositionInformation: Elements composition list is not defined or empty..."; 1013 return undef; 1014 } 1015 1016 if (!defined $Precision) { 1017 $Precision = 2; 1018 } 1019 1020 $FormattedInfo = MolecularFormula::FormatCompositionInfomation($ElementsRef, $ElementCompositionRef, $Precision); 1021 1022 return $FormattedInfo; 1023 } 1024 1025 # Copy molecule and its associated data using Storable::dclone and update: 1026 # 1027 # o Atom references corresponding atoms and bonds objects 1028 # o Bond object references 1029 # 1030 # Object IDs for Atoms and Bonds don't get changed. So there is no need to clear 1031 # up any exisiting ring data attached to molecule via graph as vertex IDs. 1032 # 1033 sub Copy { 1034 my($This) = @_; 1035 my($NewMolecule, $Atom, $NewAtom, $AtomID, @Atoms, @AtomIDs, %AtomsIDsToNewAtoms); 1036 1037 $NewMolecule = Storable::dclone($This); 1038 1039 # Update atom references stored as vertex property... 1040 1041 @Atoms = (); @AtomIDs = (); 1042 %AtomsIDsToNewAtoms = (); 1043 1044 @AtomIDs = $This->GetVertices(); 1045 if (@AtomIDs) { 1046 @Atoms = $This->GetVerticesProperty('Atom', @AtomIDs); 1047 } 1048 1049 for $Atom (@Atoms) { 1050 $AtomID = $Atom->GetID(); 1051 1052 # Setup a reference to copied atom object... 1053 $NewAtom = $Atom->Copy(); 1054 $AtomsIDsToNewAtoms{$AtomID} = $NewAtom; 1055 1056 # Update atom reference to new atom object... 1057 $NewMolecule->UpdateVertexProperty('Atom', $NewAtom, $AtomID); 1058 } 1059 1060 # Update bond object and bond atom references stored as edge property... 1061 my($Index, $AtomID1, $AtomID2, $Bond, $NewBond, $NewAtom1, $NewAtom2, @EdgesAtomsIDs); 1062 @EdgesAtomsIDs = (); 1063 @EdgesAtomsIDs = $This->GetEdges(); 1064 for ($Index = 0; $Index < $#EdgesAtomsIDs; $Index += 2) { 1065 $AtomID1 = $EdgesAtomsIDs[$Index]; $AtomID2 = $EdgesAtomsIDs[$Index + 1]; 1066 1067 # Get reference to bond object... 1068 $Bond = $This->GetEdgeProperty('Bond', $AtomID1, $AtomID2); 1069 1070 # Make a new bond object and update its atom references... 1071 $NewBond = $Bond->Copy(); 1072 $NewAtom1 = $AtomsIDsToNewAtoms{$AtomID1}; 1073 $NewAtom2 = $AtomsIDsToNewAtoms{$AtomID2}; 1074 $NewBond->SetAtoms($NewAtom1, $NewAtom2); 1075 1076 # Update bond object reference in the new molecule... 1077 $NewMolecule->UpdateEdgeProperty('Bond', $NewBond, $AtomID1, $AtomID2); 1078 } 1079 1080 return $NewMolecule; 1081 } 1082 1083 # Get number of connected components... 1084 # 1085 sub GetNumOfConnectedComponents { 1086 my($This) = @_; 1087 my($NumOfComponents); 1088 1089 $NumOfComponents = $This->GetConnectedComponentsVertices(); 1090 1091 return $NumOfComponents; 1092 } 1093 1094 # Return a reference to an array containing molecules corresponding 1095 # to connected components sorted in decreasing order of component size... 1096 # 1097 sub GetConnectedComponents { 1098 my($This) = @_; 1099 my($Index, @ComponentMolecules, @ConnectedComponents); 1100 1101 @ConnectedComponents = (); 1102 @ConnectedComponents = $This->GetConnectedComponentsVertices(); 1103 @ComponentMolecules = (); 1104 1105 for $Index (0 .. $#ConnectedComponents) { 1106 push @ComponentMolecules, $This->_GetConnectedComponent(\@ConnectedComponents, $Index); 1107 } 1108 return @ComponentMolecules; 1109 } 1110 1111 # Return a reference to largest connected component as a molecule object... 1112 # 1113 sub GetLargestConnectedComponent { 1114 my($This) = @_; 1115 my($LargestComponentIndex, @ConnectedComponents); 1116 1117 $LargestComponentIndex = 0; 1118 @ConnectedComponents = (); 1119 @ConnectedComponents = $This->GetConnectedComponentsVertices(); 1120 1121 return $This->_GetConnectedComponent(\@ConnectedComponents, $LargestComponentIndex); 1122 } 1123 1124 # Return connected component as a molecule... 1125 # 1126 sub _GetConnectedComponent { 1127 my($This, $ConnectedComponentsRef, $ComponentIndex) = @_; 1128 my($ComponentMolecule); 1129 1130 # Copy existing molecule... 1131 $ComponentMolecule = $This->Copy(); 1132 1133 # Delete all atoms besides the ones in specified component... 1134 $ComponentMolecule->_DeleteConnectedComponents($ConnectedComponentsRef, $ComponentIndex); 1135 1136 # Clear any deteced rings... 1137 if ($ComponentMolecule->HasRings()) { 1138 $ComponentMolecule->ClearRings(); 1139 } 1140 return $ComponentMolecule; 1141 } 1142 1143 # Delete atoms corresponding to all connected components except the one specified... 1144 # 1145 sub _DeleteConnectedComponents { 1146 my($This, $ConnectedComponentsRef, $KeepComponentIndex) = @_; 1147 my($Index, $AtomID); 1148 1149 INDEX: for $Index (0 .. $#{$ConnectedComponentsRef}) { 1150 if ($Index == $KeepComponentIndex) { 1151 next INDEX; 1152 } 1153 for $AtomID (@{$ConnectedComponentsRef->[$Index]}) { 1154 $This->DeleteVertex($AtomID); 1155 } 1156 } 1157 return $This; 1158 } 1159 1160 # Return an array containing references to atom arrays corresponding to atoms of 1161 # connected components sorted in order of their decreasing size... 1162 # 1163 sub GetConnectedComponentsAtoms { 1164 my($This) = @_; 1165 my($Index, @ComponentsAtoms, @ConnectedComponents); 1166 1167 @ConnectedComponents = (); 1168 @ConnectedComponents = $This->GetConnectedComponentsVertices(); 1169 1170 @ComponentsAtoms = (); 1171 for $Index (0 .. $#ConnectedComponents) { 1172 my(@ComponentAtoms); 1173 1174 @ComponentAtoms = (); 1175 @ComponentAtoms = $This->_GetConnectedComponentAtoms(\@ConnectedComponents, $Index); 1176 push @ComponentsAtoms, \@ComponentAtoms; 1177 } 1178 return @ComponentsAtoms; 1179 } 1180 1181 # Return an array containing atoms correspondig to largest connected component... 1182 # 1183 sub GetLargestConnectedComponentAtoms { 1184 my($This) = @_; 1185 my($LargestComponentIndex, @ConnectedComponents); 1186 1187 $LargestComponentIndex = 0; 1188 @ConnectedComponents = (); 1189 @ConnectedComponents = $This->GetConnectedComponentsVertices(); 1190 1191 return $This->_GetConnectedComponentAtoms(\@ConnectedComponents, $LargestComponentIndex); 1192 } 1193 1194 # Return an array containing atoms corresponding to specified connected component... 1195 # 1196 sub _GetConnectedComponentAtoms { 1197 my($This, $ConnectedComponentsRef, $ComponentIndex) = @_; 1198 my($AtomID, @AtomIDs, @ComponentAtoms); 1199 1200 @ComponentAtoms = (); 1201 @AtomIDs = (); 1202 1203 for $AtomID (@{$ConnectedComponentsRef->[$ComponentIndex]}) { 1204 push @AtomIDs, $AtomID; 1205 } 1206 @ComponentAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); 1207 1208 return @ComponentAtoms; 1209 } 1210 1211 # Except for the largest connected component, delete atoms corresponding to all other 1212 # connected components... 1213 # 1214 sub KeepLargestComponent { 1215 my($This) = @_; 1216 my($LargestComponentIndex, @ConnectedComponents); 1217 1218 @ConnectedComponents = (); 1219 @ConnectedComponents = $This->GetConnectedComponentsVertices(); 1220 if (@ConnectedComponents == 1) { 1221 return $This; 1222 } 1223 $LargestComponentIndex = 0; 1224 $This->_DeleteConnectedComponents(\@ConnectedComponents, $LargestComponentIndex); 1225 1226 # Clear any deteced rings... 1227 if ($This->HasRings()) { 1228 $This->ClearRings(); 1229 } 1230 1231 return $This; 1232 } 1233 1234 # Get an array of topologically sorted atoms starting from a specified atom or 1235 # an arbitrary atom in the molecule... 1236 # 1237 sub GetTopologicallySortedAtoms { 1238 my($This, $StartAtom) = @_; 1239 my(@SortedAtoms); 1240 1241 @SortedAtoms = (); 1242 if (defined($StartAtom) && !$This->HasAtom($StartAtom)) { 1243 carp "Warning: ${ClassName}->_GetTopologicallySortedAtoms: No atoms retrieved: Start atom doesn't exist..."; 1244 return @SortedAtoms; 1245 } 1246 my($StartAtomID, @AtomIDs); 1247 1248 @AtomIDs = (); 1249 $StartAtomID = defined($StartAtom) ? $StartAtom->GetID() : undef; 1250 1251 @AtomIDs = $This->GetTopologicallySortedVertices($StartAtomID); 1252 @SortedAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); 1253 1254 return @SortedAtoms; 1255 } 1256 1257 # Detect rings in molecule... 1258 # 1259 sub DetectRings { 1260 my($This) = @_; 1261 1262 # Use graph method to detect all cycles and associate 'em to graph as graph 1263 # and vertex properties... 1264 return $This->DetectCycles(); 1265 } 1266 1267 # Clear rings in molecule... 1268 # 1269 sub ClearRings { 1270 my($This) = @_; 1271 1272 # Use graph method to clear all cycles... 1273 $This->ClearCycles(); 1274 1275 return $This; 1276 } 1277 1278 # Setup rings type paths to use during all ring related methods. Possible values: 1279 # Independent or All. Default is to use Independent rings. 1280 # 1281 sub SetActiveRings { 1282 my($This, $RingsType) = @_; 1283 1284 if (!defined $This->SetActiveCyclicPaths($RingsType)) { 1285 return undef; 1286 } 1287 return $This; 1288 } 1289 1290 # Is it a supported aromaticity model? 1291 # 1292 sub IsSupportedAromaticityModel { 1293 my($FirstParameter, $SecondParameter) = @_; 1294 my($This, $AromaticityModel); 1295 1296 if (_IsMolecule($FirstParameter)) { 1297 ($This, $AromaticityModel) = ($FirstParameter, $SecondParameter); 1298 } 1299 else { 1300 ($This, $AromaticityModel) = (undef, $FirstParameter); 1301 } 1302 1303 return exists $CanonicalAromaticityModelNamesMap{lc($AromaticityModel)} ? 1 : 0; 1304 } 1305 1306 # Get a list of supported aromaticity model names... 1307 # 1308 sub GetSupportedAromaticityModels { 1309 return (sort values %CanonicalAromaticityModelNamesMap); 1310 } 1311 1312 # Set aromaticity model... 1313 # 1314 sub SetAromaticityModel { 1315 my($This, $AromaticityModel) = @_; 1316 1317 if (!$This->IsSupportedAromaticityModel($AromaticityModel)) { 1318 my(@SupportedModels) = $This->GetSupportedAromaticityModels(); 1319 1320 carp "Warning: ${ClassName}->SetAromaticityModel: The current release of MayaChemTools doesn't support the specified aromaticity model $AromaticityModel. Supported aromaticity models defined in AromaticityModelsData.csv file are: @SupportedModels . Using MayaChemToolsAromaticityModel..."; 1321 $AromaticityModel = 'MayaChemToolsAromaticityModel'; 1322 } 1323 1324 $This->SetProperty('AromaticityModel', $AromaticityModel); 1325 1326 return $This; 1327 } 1328 1329 # Get aromaticity model... 1330 # 1331 sub GetAromaticityModel { 1332 my($This) = @_; 1333 1334 # Is ValenceModel property explicitly set? 1335 if ($This->HasProperty('AromaticityModel')) { 1336 return $This->GetProperty('AromaticityModel'); 1337 } 1338 1339 # Used internal aromaticity model as default model... 1340 return 'MayaChemToolsAromaticityModel'; 1341 } 1342 1343 # Identify aromatic rings and ring systems in a molecule and set aromaticity for 1344 # corresponding atoms and bonds. 1345 # 1346 # What is aromaticity? [ Ref 124 ] It's in the code of the implementer, did you 1347 # say? Agree. The implementation of aromaticity varies widely across different 1348 # packages [ Ref 125 ]; additionally, the implementation details are not always 1349 # completely available, and it's not possible to figure out the exact implementation 1350 # of aromaticity across various packages. Using the publicly available information, 1351 # however, one can try to reproduce the available results to the extent possible, 1352 # along with parameterizing all the control parameters used to implement different 1353 # aromaticity models, and that's exactly what the current release of MayaChemTools 1354 # does. 1355 # 1356 # The implementation of aromaticity corresponding to various aromaticity models in 1357 # MayaChemTools package is driven by an external CSV file AromaticityModelsData.csv, 1358 # which is distributed with the package and is available in lib/data directory. The CSV 1359 # files contains names of supported aromaticity models, along with various control 1360 # parameters and their values. This file is loaded and processed during instantiation 1361 # of Molecule class and data corresponding to specific aromaticity model are used 1362 # to detect aromaticity for that model. Any new aromaticity model added to the 1363 # aromaticity data file, using different combinations of values for existing control 1364 # parameters would work without any changes to the code; the addition of any new 1365 # control parameters, however, requires its implementation in the code used to 1366 # calculate number of pi electrons available towards delocalization in a ring or ring 1367 # systems. 1368 # 1369 # The current release of MayaChemTools package supports these aromaticity 1370 # models: MDLAromaticityModel, TriposAromaticityModel, MMFFAromaticityModel, 1371 # ChemAxonBasicAromaticityModel, ChemAxonGeneralAromaticityModel, 1372 # DaylightAromaticityModel, MayaChemToolsAromaticityModel. 1373 # 1374 # The current list of control parameters available to detect aromaticity corresponding 1375 # to different aromaticity models are: AllowHeteroRingAtoms, HeteroRingAtomsList, 1376 # AllowExocyclicDoubleBonds, AllowHomoNuclearExocyclicDoubleBonds, 1377 # AllowElectronegativeRingAtomExocyclicDoubleBonds, AllowRingAtomFormalCharge, 1378 # AllowHeteroRingAtomFormalCharge, MinimumRingSize. The values for these control 1379 # parameters are specified in AromaticityModelsData.csv file. 1380 # 1381 # Although definition of aromaticity differs across various aromaticity models, a ring 1382 # or a ring system containing 4n + 2 pi electrons (Huckel's rule) corresponding to 1383 # alternate single and double bonds, in general, is considered aromatic. 1384 # 1385 # The available valence free electrons on heterocyclic ring atoms, involved in two single 1386 # ring bonds, are also allowed to participate in pi electron delocalizaiton for most of 1387 # the supported aromaticity models. 1388 # 1389 # The presence of exocyclic terminal double bond on ring atoms involved in pi electron 1390 # delocalization is only allowed for some of the aromaticity models. Additionally, the type 1391 # atoms involved in exocyclic terminal double bonds may result in making a ring or ring 1392 # system non-aromatic. 1393 # 1394 sub DetectAromaticity { 1395 my($This) = @_; 1396 1397 # Delete aromaticity property for atoms and bonds... 1398 $This->DeleteAromaticity(); 1399 1400 # Any ring out there... 1401 if (!$This->HasRings()) { 1402 return $This; 1403 } 1404 1405 if ($This->HasFusedRings()) { 1406 $This->_DetectAromaticityUsingFusedRingSets(); 1407 } 1408 else { 1409 $This->_DetectAromaticityUsingIndividualRings(); 1410 } 1411 return $This; 1412 } 1413 1414 # Go over all rings and set aromaticity property for corresponding ring atoms 1415 # and bonds involved in aromatic rings... 1416 # 1417 sub _DetectAromaticityUsingIndividualRings { 1418 my($This) = @_; 1419 1420 return $This->_DetectRingsAromaticity($This->GetRings()); 1421 } 1422 1423 # For each fused ring set, detect aromaticity by treating all of its ring as one aromatic 1424 # system for counting pi electrons to satisfy Huckel's rule; In case of a failure, rings in 1425 # fused set are treated individually for aromaticity detection. Additionally, non-fused 1426 # rings are handled on their own during aromaticity detection. 1427 # 1428 # Note: 1429 # . pi electrons in common bonds involved in fused ring sets are only counted once. 1430 # 1431 # 1432 sub _DetectAromaticityUsingFusedRingSets { 1433 my($This) = @_; 1434 my($Index, $RingAtomsRef, $FusedRingSetRef, $FusedRingSetsRef, $NonFusedRingsRef, @FusedRingSetIsAromatic); 1435 1436 ($FusedRingSetsRef, $NonFusedRingsRef) = $This->GetFusedAndNonFusedRings(); 1437 1438 @FusedRingSetIsAromatic = (); 1439 RINGSET: for $Index (0 .. $#{$FusedRingSetsRef}) { 1440 $FusedRingSetRef = $FusedRingSetsRef->[$Index]; 1441 $FusedRingSetIsAromatic[$Index] = 0; 1442 1443 my($NumOfPiElectronsInRingSet, $NumOfPiElectronsInRing, $Bond, $BondID, %FusedRingSetsBondsMap, %FusedRingSetsBondsVisitedMap, %FusedRingBondsMap); 1444 1445 $NumOfPiElectronsInRingSet = 0; 1446 1447 %FusedRingSetsBondsMap = (); 1448 %FusedRingSetsBondsVisitedMap = (); 1449 %FusedRingBondsMap = (); 1450 1451 # Setup a bond ID map for all bonds in fused ring set and another one 1452 # for bonds involved in more than one ring... 1453 # 1454 for $RingAtomsRef (@{$FusedRingSetRef}) { 1455 for $Bond ($This->GetRingBonds(@{$RingAtomsRef})) { 1456 $BondID = $Bond->GetID(); 1457 $FusedRingSetsBondsMap{$BondID} = $BondID; 1458 1459 if ($Bond->GetNumOfRings() == 2) { 1460 $FusedRingBondsMap{$BondID} = $BondID; 1461 } 1462 } 1463 } 1464 1465 for $RingAtomsRef (@{$FusedRingSetRef}) { 1466 my(@RingBonds); 1467 1468 @RingBonds = (); 1469 @RingBonds = $This->GetRingBonds(@{$RingAtomsRef}); 1470 $NumOfPiElectronsInRing = $This->_GetNumOfPiElectronsAvailableForDelocalization($RingAtomsRef, \@RingBonds, \%FusedRingSetsBondsMap, \%FusedRingSetsBondsVisitedMap, \%FusedRingBondsMap); 1471 1472 if (!$NumOfPiElectronsInRing) { 1473 next RINGSET; 1474 } 1475 $NumOfPiElectronsInRingSet += $NumOfPiElectronsInRing; 1476 } 1477 if ($This->_DoPiElectronSatifyHuckelsRule($NumOfPiElectronsInRingSet)) { 1478 $FusedRingSetIsAromatic[$Index] = 1; 1479 } 1480 } 1481 1482 # Set atom and bond aromatic flags for ring sets whose pi electrons satisfy Huckel's rule; otherwise, 1483 # treat rings in a ring set as individual rings for detecting aromaticity... 1484 for $Index (0 .. $#{$FusedRingSetsRef}) { 1485 $FusedRingSetRef = $FusedRingSetsRef->[$Index]; 1486 if ($FusedRingSetIsAromatic[$Index]) { 1487 $This->_SetRingsAromaticity(@{$FusedRingSetRef}); 1488 } 1489 else { 1490 $This->_DetectRingsAromaticity(@{$FusedRingSetRef}); 1491 } 1492 } 1493 1494 $This->_DetectRingsAromaticity(@{$NonFusedRingsRef}); 1495 1496 return $This; 1497 } 1498 1499 # Detect and set aromaticity for rings... 1500 # 1501 sub _DetectRingsAromaticity { 1502 my($This, @Rings) = @_; 1503 my($RingAtom, $RingBond, $RingAtomsRef); 1504 1505 RING: for $RingAtomsRef (@Rings) { 1506 if (!$This->_CheckRingAromaticity(@{$RingAtomsRef})) { 1507 next RING; 1508 } 1509 $This->_SetRingAromaticity(@{$RingAtomsRef}); 1510 } 1511 return $This; 1512 } 1513 1514 # Set aromatic property for all all atoms and bonds involved in all specified rings.. 1515 # 1516 sub _SetRingsAromaticity { 1517 my($This, @Rings) = @_; 1518 my($RingAtomsRef ); 1519 1520 for $RingAtomsRef (@Rings) { 1521 $This->_SetRingAromaticity(@{$RingAtomsRef}); 1522 } 1523 return $This; 1524 } 1525 1526 # Set aromatic property for all all atoms and bonds involved in ring.. 1527 # 1528 sub _SetRingAromaticity { 1529 my($This, @RingAtoms) = @_; 1530 my($RingAtom, $RingBond); 1531 1532 for $RingAtom (@RingAtoms) { 1533 $RingAtom->SetAromatic(1); 1534 } 1535 for $RingBond ($This->GetRingBonds(@RingAtoms)) { 1536 $RingBond->SetAromatic(1); 1537 } 1538 return $This; 1539 } 1540 1541 1542 # For a ring to be an aromatic ring, all of its atoms must have aromatic property 1543 # set. 1544 # 1545 sub IsRingAromatic { 1546 my($This, @RingAtoms) = @_; 1547 my($RingAtom); 1548 1549 for $RingAtom (@RingAtoms) { 1550 if (!$RingAtom->IsAromatic()) { 1551 return 0; 1552 } 1553 } 1554 return 1; 1555 } 1556 1557 # Delete aromatic property for all atoms and bonds... 1558 # 1559 sub DeleteAromaticity { 1560 my($This) = @_; 1561 1562 return $This->_DeleteAtomsAndBondsAromaticity(); 1563 } 1564 1565 # Check ring aromaticity... 1566 # 1567 sub _CheckRingAromaticity { 1568 my($This, @RingAtoms) = @_; 1569 my($NumOfPiElectrons, $BondID, @RingBonds); 1570 1571 @RingBonds = (); 1572 @RingBonds = $This->GetRingBonds(@RingAtoms); 1573 1574 $NumOfPiElectrons = $This->_GetNumOfPiElectronsAvailableForDelocalization(\@RingAtoms, \@RingBonds); 1575 1576 return $This->_DoPiElectronSatifyHuckelsRule($NumOfPiElectrons); 1577 } 1578 1579 # Get number of pi electrons available for delocalizaiton in a ring or ring system... 1580 # 1581 sub _GetNumOfPiElectronsAvailableForDelocalization { 1582 my($This, $RingAtomsRef, $RingBondsRef, $FusedRingSetsBondsMapRef, $FusedRingSetsBondsVisitedMapRef, $FusedRingBondsMapRef) = @_; 1583 my($AromaticityModelName, $AromaticityModelDataRef, $ExocyclicDoubleBondsDataMapRef, $NumOfConjugatedDoubleBonds, $NumOfExplicitAromaticBonds, $NumOfRingAtomElectronPairs, $NumOfRingBondsProcessed, $NumOfPiElectrons, $Index, $RingBond, $RingAtom, $RingAtomSymbol, $BondOrder, $RingAtomID, $RingBondID, $PreviousIndex, $PreviousRingBond, $ExcludeFreeRadicalElectrons, %ElectronPairContributionProcessedMap); 1584 1585 $AromaticityModelName = $CanonicalAromaticityModelNamesMap{lc($This->GetAromaticityModel())}; 1586 $AromaticityModelDataRef = \%{$AromaticityModelsDataMap{$AromaticityModelName}}; 1587 1588 # Perform an intial check for potential atomatic ring atoms.. 1589 if (!$This->_CheckRingAtomsForPotentialAromaticity($RingAtomsRef, $RingBondsRef, $AromaticityModelDataRef)) { 1590 return 0; 1591 } 1592 1593 $ExocyclicDoubleBondsDataMapRef = undef; 1594 $ExcludeFreeRadicalElectrons = 1; 1595 1596 %ElectronPairContributionProcessedMap = (); 1597 1598 ($NumOfPiElectrons, $NumOfRingBondsProcessed, $NumOfConjugatedDoubleBonds, $NumOfExplicitAromaticBonds, $NumOfRingAtomElectronPairs) = ('0') x 5; 1599 1600 # Go over ring atoms and bonds to check their participation in aromaticity and count 1601 # pi electrons available for delocalization corresponding to various aromaticity models... 1602 # 1603 RINGBOND: for $Index (0 .. $#{$RingBondsRef}) { 1604 $RingBond = $RingBondsRef->[$Index]; 1605 $RingAtom = $RingAtomsRef->[$Index]; 1606 $BondOrder = $RingBond->GetBondOrder(); 1607 1608 # Is this ring bond part of a fused ring system which has been already processed? 1609 if (defined($FusedRingSetsBondsVisitedMapRef) && $RingBond->GetNumOfRings() == 2) { 1610 $RingBondID = $RingBond->GetID(); 1611 if (exists $FusedRingSetsBondsVisitedMapRef->{$RingBondID}) { 1612 next RINGBOND; 1613 } 1614 $FusedRingSetsBondsVisitedMapRef->{$RingBondID} = $RingBondID; 1615 } 1616 $NumOfRingBondsProcessed++; 1617 1618 # For first ring, previous ring bond corrresponds to last ring bond... 1619 $PreviousIndex = $Index ? ($Index -1) : $#{$RingBondsRef}; 1620 $PreviousRingBond = $RingBondsRef->[$PreviousIndex]; 1621 1622 # Check for presence of alternate single/double bond configuration, and pesence of 1623 # hetero atoms with two single ring bonds along with any exocyclic double bonds... 1624 # 1625 BONDORDER: { 1626 # Is current ring double bond in an alternate single/double bond configuration? 1627 if ($BondOrder == 2) { 1628 if ($PreviousRingBond->GetBondOrder() != 1) { 1629 return 0; 1630 } 1631 $NumOfConjugatedDoubleBonds += 1; 1632 last BONDORDER; 1633 } 1634 1635 # Is current ring bond order correspond to an explicit aromatic bond? 1636 if ($BondOrder == 1.5) { 1637 if ($PreviousRingBond->GetBondOrder() != 1.5) { 1638 return 0; 1639 } 1640 $NumOfExplicitAromaticBonds += 1; 1641 last BONDORDER; 1642 } 1643 1644 # Check for potential hetero atoms involved in two single ring bonds along 1645 # with any terminal exocyclic bonds... 1646 if ($BondOrder == 1) { 1647 if ($PreviousRingBond->GetBondOrder() != 1) { 1648 # Part of a conjugated system... 1649 last BONDORDER; 1650 } 1651 1652 # Identify any exocylic bonds on rings atoms... 1653 if (!defined $ExocyclicDoubleBondsDataMapRef) { 1654 $ExocyclicDoubleBondsDataMapRef = $This->_IdentifyRingAtomsInvolvedInExocyclicDoubleBonds($RingAtomsRef, $RingBondsRef, $FusedRingSetsBondsMapRef); 1655 } 1656 1657 # Is current ring atom part of an allowed exocyclic terminal bond? 1658 if (!$This->_CheckPotentialAromaticRingAtomForExocylicDoubleBonds($RingAtom, $AromaticityModelDataRef, $ExocyclicDoubleBondsDataMapRef)) { 1659 return 0; 1660 } 1661 1662 # Is it allowed to have any formal charge? 1663 if (!$This->_CheckPotentialAromaticRingAtomForFormalCharge($RingAtom, $AromaticityModelDataRef)) { 1664 return 0; 1665 } 1666 1667 # It it an allowed hetero ring atom or a carbon atom? 1668 if (!$This->_CheckPotentialAromaticRingAtomForAllowedHeteroAtoms($RingAtom, $AromaticityModelDataRef)) { 1669 return 0; 1670 } 1671 1672 $RingAtomID = $RingAtom->GetID(); 1673 $ElectronPairContributionProcessedMap{$RingAtomID} = $RingAtomID; 1674 1675 # Is it able to donate a pair for electrons towards pi electron delocalization? 1676 if ($RingAtom->GetValenceFreeElectrons($ExcludeFreeRadicalElectrons) >= 2) { 1677 # Possibilites: 1678 # . Hetero atom with or without formal charge and an available electron pair 1679 # . Carbon atom with -ve formal charge and with an available electron pair 1680 # 1681 $NumOfRingAtomElectronPairs += 1; 1682 } 1683 else { 1684 # Is ring atom involved in two single bonds without any electron pair allowed? 1685 if (!$This->_AllowRingAtomInTwoSingleBondsWithoutElectronPair($RingAtom, $RingBond, $PreviousRingBond, $ExocyclicDoubleBondsDataMapRef, $FusedRingBondsMapRef)) { 1686 return 0; 1687 } 1688 } 1689 last BONDORDER; 1690 } 1691 1692 # Any other type of ring atom/bond is not allowed to contribute towards pi electron count 1693 # and caused loss of aromaticity... 1694 return 0; 1695 } 1696 } 1697 1698 # Check for any electron pair contributions towards pi electron delocalization due to 1699 # -ve formal charge on ring atoms which haven't been already processed and part of 1700 # conjugated single/double bond system... 1701 # 1702 $NumOfRingAtomElectronPairs += $This->_GetElectronPairsContributionFromConjugatedRingAtoms($RingAtomsRef, $RingBondsRef, $ExcludeFreeRadicalElectrons, $AromaticityModelDataRef, \%ElectronPairContributionProcessedMap); 1703 1704 # Setup pi electron count available for delocalization... 1705 COUNT: { 1706 if ($NumOfExplicitAromaticBonds == $NumOfRingBondsProcessed) { 1707 # Each aromatic bond contribute one electron towards pi electron delocalization... 1708 $NumOfPiElectrons = $NumOfExplicitAromaticBonds; 1709 last COUNT; 1710 } 1711 1712 # Each conjugated double bond contribute two electrons towards pi electron delocalization... 1713 $NumOfPiElectrons = 2*$NumOfConjugatedDoubleBonds + 2*$NumOfRingAtomElectronPairs; 1714 } 1715 1716 return $NumOfPiElectrons; 1717 } 1718 1719 # Check ring atoms for their potential participation in aromatic systems.. 1720 # 1721 sub _CheckRingAtomsForPotentialAromaticity { 1722 my($This, $RingAtomsRef, $RingBondsRef, $AromaticityModelDataRef) = @_; 1723 my($Index, $RingBond, $RingAtom); 1724 1725 # Check availability of ring atoms and bonds... 1726 if (!(defined($RingAtomsRef) && @{$RingBondsRef})) { 1727 return 0; 1728 } 1729 1730 # Is there any minimum ring size limit? 1731 if ($AromaticityModelDataRef->{MinimumRingSize}) { 1732 if (@{$RingAtomsRef} < $AromaticityModelDataRef->{MinimumRingSize}) { 1733 return 0; 1734 } 1735 } 1736 1737 # Make sure ring bond order is not greater than 2 and ring atom is not connected to more 1738 # than 3 other atoms to eliminate any non sp2 carbon atoms and still allow for hetero atoms 1739 # to contrbute towards electron delocalization... 1740 # 1741 for $Index (0 .. $#{$RingBondsRef}) { 1742 $RingBond = $RingBondsRef->[$Index]; 1743 $RingAtom = $RingAtomsRef->[$Index]; 1744 1745 if (($RingBond->GetBondOrder() > 2) || ($RingAtom->GetNumOfBonds() + $RingAtom->GetNumOfMissingHydrogens()) > 3) { 1746 return 0; 1747 } 1748 } 1749 1750 return 1; 1751 } 1752 1753 # Identify any exocylic double bonds on ring atoms... 1754 # 1755 sub _IdentifyRingAtomsInvolvedInExocyclicDoubleBonds { 1756 my($This, $RingAtomsRef, $RingBondsRef, $FusedRingSetsBondsMapRef) = @_; 1757 my($Index, $RingAtom, $RingBond, $RingAtomID, $Bond, $BondID, $BondedAtom, $RingBondsMapRef, %RingBondsMap, %ExocyclicDoubleBondsDataMap); 1758 1759 # Setup a ring bond map to process exocyclic bonds... 1760 $RingBondsMapRef = undef; 1761 %RingBondsMap = (); 1762 1763 if (defined $FusedRingSetsBondsMapRef) { 1764 $RingBondsMapRef = $FusedRingSetsBondsMapRef; 1765 } 1766 else { 1767 for $BondID (map { $_->GetID() } @{$RingBondsRef}) { 1768 $RingBondsMap{$BondID} = $BondID; 1769 } 1770 $RingBondsMapRef = \%RingBondsMap; 1771 } 1772 1773 # Intialize exocyclic terminal double bond data... 1774 %ExocyclicDoubleBondsDataMap = (); 1775 %{$ExocyclicDoubleBondsDataMap{RingAtomID}} = (); 1776 1777 for $Index (0 .. $#{$RingBondsRef}) { 1778 $RingBond = $RingBondsRef->[$Index]; 1779 $RingAtom = $RingAtomsRef->[$Index]; 1780 1781 $RingAtomID = $RingAtom->GetID(); 1782 1783 BOND: for $Bond ($RingAtom->GetBonds()) { 1784 if ($Bond->GetBondOrder != 2) { 1785 next BOND; 1786 } 1787 1788 # Is it part of ring or ring system under consideration? 1789 if (exists $RingBondsMapRef->{$Bond->GetID()}) { 1790 next BOND; 1791 } 1792 1793 # Is bonded atom in a ring or a non-terminal atom? 1794 $BondedAtom = $Bond->GetBondedAtom($RingAtom); 1795 if ($BondedAtom->IsInRing() || !$BondedAtom->IsTerminal() ) { 1796 next BOND; 1797 } 1798 1799 # Track exocyclic terminal double bond information... 1800 if (!exists $ExocyclicDoubleBondsDataMap{RingAtomID}{$RingAtomID}) { 1801 @{$ExocyclicDoubleBondsDataMap{RingAtomID}{$RingAtomID}} = (); 1802 } 1803 push @{$ExocyclicDoubleBondsDataMap{RingAtomID}{$RingAtomID}}, $BondedAtom; 1804 } 1805 } 1806 1807 return \%ExocyclicDoubleBondsDataMap; 1808 } 1809 1810 # Check to see whether ring atoms are allowed to participate in exocyclic terminal double 1811 # bonds... 1812 # 1813 sub _CheckPotentialAromaticRingAtomForExocylicDoubleBonds { 1814 my($This, $RingAtom, $AromaticityModelDataRef, $ExocyclicDoubleBondsDataMapRef) = @_; 1815 my($RingAtomID, $ExocyclicTerminalAtom, $RingAtomElectronegativity, $TerminalAtomElectronagativity); 1816 1817 $RingAtomID = $RingAtom->GetID(); 1818 1819 # Is it part of an exocyclic terminal double bond? 1820 if (!exists $ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtomID}) { 1821 return 1; 1822 } 1823 1824 # Are exocyclic terminal double bonds allowed? 1825 if (!$AromaticityModelDataRef->{AllowExocyclicDoubleBonds}) { 1826 return 0; 1827 } 1828 1829 # Are there multiple exocyclic double bonds? 1830 if (@{$ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtomID}} > 1) { 1831 return 0; 1832 } 1833 ($ExocyclicTerminalAtom) = @{$ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtomID}}; 1834 1835 # Are homo nuclear exocyclic terminal double bonds allowed? 1836 if (!$AromaticityModelDataRef->{AllowHomoNuclearExocyclicDoubleBonds}) { 1837 if ($RingAtom->GetAtomicNumber() == $ExocyclicTerminalAtom->GetAtomicNumber()) { 1838 return 0; 1839 } 1840 } 1841 1842 # Are ring atoms with higher electronegativity allowed in exocyclic double bonds? 1843 if (!$AromaticityModelDataRef->{AllowElectronegativeRingAtomExocyclicDoubleBonds}) { 1844 $RingAtomElectronegativity = PeriodicTable::GetElementPaulingElectronegativity($RingAtom->GetAtomicNumber()); 1845 $TerminalAtomElectronagativity = PeriodicTable::GetElementPaulingElectronegativity($ExocyclicTerminalAtom->GetAtomicNumber()); 1846 1847 if ($RingAtomElectronegativity && $TerminalAtomElectronagativity) { 1848 if ($RingAtomElectronegativity > $TerminalAtomElectronagativity) { 1849 return 0; 1850 } 1851 } 1852 } 1853 1854 return 1; 1855 } 1856 1857 # 1858 # Check for any formal charge participation into electron delocalization... 1859 # 1860 sub _CheckPotentialAromaticRingAtomForFormalCharge { 1861 my($This, $RingAtom, $AromaticityModelDataRef) = @_; 1862 my($FormalCharge); 1863 1864 # Does atom has any formal charge? 1865 $FormalCharge = $RingAtom->GetFormalCharge(); 1866 if (!$FormalCharge) { 1867 return 1; 1868 } 1869 1870 # Are ring atoms with formal charge allowed to participate in electron delocalization? 1871 if (!$AromaticityModelDataRef->{AllowRingAtomFormalCharge}) { 1872 return 0; 1873 } 1874 1875 # Are hetero ring atoms with formal charge allowed to participate in electron delocalization? 1876 if (!$RingAtom->IsCarbon()) { 1877 if (!$AromaticityModelDataRef->{AllowHeteroRingAtomFormalCharge}) { 1878 return 0; 1879 } 1880 } 1881 1882 return 1; 1883 } 1884 1885 # 1886 # Check ring atoms for allowed hetero atoms... 1887 # 1888 sub _CheckPotentialAromaticRingAtomForAllowedHeteroAtoms { 1889 my($This, $RingAtom, $AromaticityModelDataRef) = @_; 1890 my($RingAtomSymbol); 1891 1892 # Is it a Carbon atom? 1893 if ($RingAtom->IsCarbon()) { 1894 return 1; 1895 } 1896 1897 # Are heteroatoms allowed? 1898 if (!$AromaticityModelDataRef->{AllowHeteroRingAtoms}) { 1899 return 0; 1900 } 1901 1902 # Is it an allowed hetero atom? 1903 $RingAtomSymbol = $RingAtom->GetAtomSymbol(); 1904 if (!exists $AromaticityModelDataRef->{HeteroRingAtomsListMapRef}->{$RingAtomSymbol}) { 1905 return 0; 1906 } 1907 1908 return 1; 1909 } 1910 1911 # Check for any electron pair contributions toward pi electron delocalization due to 1912 # -ve formal charge on ring atoms which haven't been already processed and part of 1913 # conjugated single/double bond system... 1914 # 1915 sub _GetElectronPairsContributionFromConjugatedRingAtoms { 1916 my($This, $RingAtomsRef, $RingBondsRef, $ExcludeFreeRadicalElectrons, $AromaticityModelDataRef, $ElectronPairContributionProcessedMapRef) = @_; 1917 my($Index, $RingBond, $RingAtom, $NumOfRingAtomElectronPairs, $RingAtomID); 1918 1919 # Is formal charge allowed on ring atoms? 1920 if (!$AromaticityModelDataRef->{AllowRingAtomFormalCharge}) { 1921 return 0; 1922 } 1923 1924 $NumOfRingAtomElectronPairs = 0; 1925 1926 # Process ring atoms... 1927 RINGBOND: for $Index (0 .. $#{$RingBondsRef}) { 1928 $RingBond = $RingBondsRef->[$Index]; 1929 $RingAtom = $RingAtomsRef->[$Index]; 1930 $RingAtomID = $RingAtom->GetID(); 1931 1932 # Is is already processed? 1933 if (exists $ElectronPairContributionProcessedMapRef->{$RingAtomID}) { 1934 next RINGBOND; 1935 } 1936 $ElectronPairContributionProcessedMapRef->{$RingAtomID} = $RingAtomID; 1937 1938 # Is it allowed to have any formal charge? 1939 if (!$This->_CheckPotentialAromaticRingAtomForFormalCharge($RingAtom, $AromaticityModelDataRef)) { 1940 next RINGBOND; 1941 } 1942 1943 # It it an allowed hetero ring atom or a carbon atom? 1944 if (!$This->_CheckPotentialAromaticRingAtomForAllowedHeteroAtoms($RingAtom, $AromaticityModelDataRef)) { 1945 next RINGBOND; 1946 } 1947 1948 # It is an atom with -ve formal charge? 1949 if ($RingAtom->GetFormalCharge() >= 0) { 1950 next RINGBOND; 1951 } 1952 1953 # Is it able to donate a pair for electrons towards pi electron delocalization? 1954 if ($RingAtom->GetValenceFreeElectrons($ExcludeFreeRadicalElectrons) < 2) { 1955 next RINGBOND; 1956 } 1957 $NumOfRingAtomElectronPairs += 1; 1958 } 1959 1960 return $NumOfRingAtomElectronPairs; 1961 } 1962 1963 # Check for ring atoms involved in two single ring bonds without any available electron 1964 # pair which are allowed to participate in aromatic system, after all other checks 1965 # corresponding to specified aromaticity models have already been performed... 1966 # 1967 sub _AllowRingAtomInTwoSingleBondsWithoutElectronPair { 1968 my($This, $RingAtom, $RingBond, $PreviousRingBond, $ExocyclicDoubleBondsDataMapRef, $FusedRingBondsMapRef) = @_; 1969 1970 ALLOWRINGATOM: { 1971 if (exists $ExocyclicDoubleBondsDataMapRef->{RingAtomID}{$RingAtom->GetID()}) { 1972 # Ring atom in an exocylic terminal double bond without any available electron pair... 1973 last ALLOWRINGATOM; 1974 } 1975 1976 if ($RingAtom->GetFormalCharge() > 0) { 1977 # Ring atom with positive formal charge without any available electron pair... 1978 last ALLOWRINGATOM; 1979 } 1980 1981 if (defined $FusedRingBondsMapRef && (exists $FusedRingBondsMapRef->{$RingBond->GetID()} || exists $FusedRingBondsMapRef->{$PreviousRingBond->GetID()})) { 1982 # Ring atom involved in fused ring bond, which might end up being part of a conjugated 1983 # system in another fused ring... 1984 last ALLOWRINGATOM; 1985 } 1986 1987 # Ring atom in any other environment is not allowed... 1988 return 0; 1989 } 1990 1991 return 1; 1992 } 1993 1994 # Do pi electrons satify huckel's rule: Number of pi electrons correspond to 4n + 2 where 1995 # n is a positive integer... 1996 # 1997 sub _DoPiElectronSatifyHuckelsRule { 1998 my($This, $NumOfPiElectrons) = @_; 1999 2000 $NumOfPiElectrons = $NumOfPiElectrons - 2; 2001 2002 return ($NumOfPiElectrons > 0) ? (($NumOfPiElectrons % 4) ? 0 : 1) : 0; 2003 } 2004 2005 # Delete aromatic property for all atoms and bonds... 2006 # 2007 sub _DeleteAtomsAndBondsAromaticity { 2008 my($This) = @_; 2009 my($Atom, $Bond); 2010 2011 for $Atom ($This->GetAtoms()) { 2012 $Atom->DeleteAromatic(); 2013 } 2014 for $Bond ($This->GetBonds()) { 2015 $Bond->DeleteAromatic(); 2016 } 2017 return $This; 2018 } 2019 2020 # Kekulize marked ring and non-ring aromatic atoms in a molecule... 2021 # 2022 sub KekulizeAromaticAtoms { 2023 my($This) = @_; 2024 2025 if (!$This->_KekulizeAromaticAtomsInRings()) { 2026 return 0; 2027 } 2028 2029 if (!$This->_KekulizeAromaticAtomsNotInRings()) { 2030 return 0; 2031 } 2032 2033 return 1; 2034 } 2035 2036 # Kekulize marked aromatic atoms in rings and fused ring sets... 2037 # 2038 sub _KekulizeAromaticAtomsInRings { 2039 my($This) = @_; 2040 2041 if (!$This->HasRings()) { 2042 # Nothing to do... 2043 return 1; 2044 } 2045 2046 if (!$This->HasAromaticAtomsInRings()) { 2047 # Nothing to do... 2048 return 1; 2049 } 2050 2051 # Identify fully aromatic fused and individual rings along with any partially aromatic ring components 2052 # using marked aromatic atoms in a molecule and kekulize them as individual stes... 2053 # 2054 my($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = (undef) x 3; 2055 if ($This->HasFusedRings()) { 2056 ($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = $This->_GetFusedAndNonFusedRingsContainingAromaticAtoms(); 2057 } 2058 else { 2059 ($AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = $This->_GetIndividualRingsContainingAromaticAtoms(); 2060 } 2061 2062 return $This->_KekulizeCompleteAndPartialAromaticRings($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef); 2063 } 2064 2065 # Identify fully aromatic fused and individual rings along with any partially aromatic ring components 2066 # using marked aromatic atoms in a molecule... 2067 # 2068 sub _GetFusedAndNonFusedRingsContainingAromaticAtoms { 2069 my($This) = @_; 2070 my($Index, $SetAtomsCount, $SetAromaticAtomsCount, $FusedRingSetRef, $FusedRingSetsRef, $NonFusedRingsRef, $IndividualRingsRef, $RingAtomsRef, $RingAtomsCount, $AromaticAtomsCount, $RingAtom, $NonFusedFullyAromaticRingsRef, $NonFusedPartiallyAromaticRingComponentsRef, $PartiallyAromaticRingComponentsRef, @FullyAromaticFusedRingSets, @PotentialFullyAromaticRings, @FullyAromaticRings, @PotentialPartiallyAromaticRings, @PartiallyAromaticRingComponents); 2071 2072 @FullyAromaticFusedRingSets = (); 2073 2074 @PotentialFullyAromaticRings = (); 2075 @FullyAromaticRings = (); 2076 2077 @PotentialPartiallyAromaticRings = (); 2078 @PartiallyAromaticRingComponents = (); 2079 2080 ($FusedRingSetsRef, $NonFusedRingsRef) = $This->GetFusedAndNonFusedRings(); 2081 2082 # Go over fused ring sets... 2083 RINGSET: for $Index (0 .. $#{$FusedRingSetsRef}) { 2084 $FusedRingSetRef = $FusedRingSetsRef->[$Index]; 2085 2086 $SetAtomsCount = 0; 2087 $SetAromaticAtomsCount = 0; 2088 2089 for $RingAtomsRef (@{$FusedRingSetRef}) { 2090 $SetAtomsCount += scalar @{$RingAtomsRef}; 2091 2092 for $RingAtom (@{$RingAtomsRef}) { 2093 if ($RingAtom->IsAromatic()) { 2094 $SetAromaticAtomsCount += 1; 2095 } 2096 } 2097 } 2098 2099 if (!($SetAtomsCount && $SetAromaticAtomsCount)) { 2100 next RINGSET; 2101 } 2102 2103 if ($SetAromaticAtomsCount == $SetAtomsCount) { 2104 push @FullyAromaticFusedRingSets, $FusedRingSetRef; 2105 } 2106 else { 2107 # Identify any individual rings in partial aromatic fused ring sets which might be 2108 # fully or partially aromatic... 2109 # 2110 RING: for $RingAtomsRef (@{$FusedRingSetRef}) { 2111 $RingAtomsCount = scalar @{$RingAtomsRef}; 2112 $AromaticAtomsCount = 0; 2113 2114 RINGATOM: for $RingAtom (@{$RingAtomsRef}) { 2115 if (!$RingAtom->IsAromatic()) { 2116 next RINGATOM; 2117 } 2118 $AromaticAtomsCount += 1; 2119 } 2120 2121 if (!($RingAtomsCount && $AromaticAtomsCount)) { 2122 next RING; 2123 } 2124 2125 if ($RingAtomsCount == $AromaticAtomsCount) { 2126 push @PotentialFullyAromaticRings, $RingAtomsRef; 2127 } 2128 else { 2129 # Track partially aromatic rings in an different list before removing them for 2130 # any overlap with other rings and then add to fully aromatic rings... 2131 push @PotentialPartiallyAromaticRings, $RingAtomsRef; 2132 } 2133 } 2134 } 2135 } 2136 2137 if (@PotentialFullyAromaticRings > 1) { 2138 # Get any fully aromatic fused ring subsets from potentially fully aromatic rings... 2139 my($FullyAromaticFusedRingSetsRefs, $FullyAromaticNonFusedRingsRef); 2140 ($FullyAromaticFusedRingSetsRefs, $FullyAromaticNonFusedRingsRef) = $This->_GetFullyAromaticFusedAndNonFusedRingsInFusedSubset(\@PotentialFullyAromaticRings); 2141 2142 if (@{$FullyAromaticFusedRingSetsRefs}) { 2143 push @FullyAromaticFusedRingSets, @{$FullyAromaticFusedRingSetsRefs}; 2144 } 2145 if (@{$FullyAromaticNonFusedRingsRef}) { 2146 push @FullyAromaticRings, @{$FullyAromaticNonFusedRingsRef}; 2147 } 2148 } 2149 else { 2150 push @FullyAromaticRings, @PotentialFullyAromaticRings; 2151 } 2152 2153 # Go over partial aromatic ring components... 2154 if (@PotentialPartiallyAromaticRings) { 2155 $PartiallyAromaticRingComponentsRef = $This->_GetPartiallyAromaticRingComponents(\@PotentialPartiallyAromaticRings, \@PotentialFullyAromaticRings); 2156 if (@{$PartiallyAromaticRingComponentsRef}) { 2157 push @PartiallyAromaticRingComponents, @{$PartiallyAromaticRingComponentsRef}; 2158 } 2159 } 2160 2161 # Go over non-fused rings... 2162 if (@{$NonFusedRingsRef}) { 2163 ($NonFusedFullyAromaticRingsRef, $NonFusedPartiallyAromaticRingComponentsRef) = $This->_GetRingsContainingAromaticAtoms(@{$NonFusedRingsRef}); 2164 2165 if (@{$NonFusedFullyAromaticRingsRef}) { 2166 push @FullyAromaticRings, @{$NonFusedFullyAromaticRingsRef}; 2167 } 2168 if (@{$NonFusedPartiallyAromaticRingComponentsRef}) { 2169 push @PartiallyAromaticRingComponents, @{$NonFusedPartiallyAromaticRingComponentsRef}; 2170 } 2171 } 2172 2173 return (\@FullyAromaticFusedRingSets, \@FullyAromaticRings, \@PartiallyAromaticRingComponents); 2174 } 2175 2176 # Identify fully aromatic fused sets and non-fused rings in potentially fully aromatic 2177 # rings in fused ring sets... 2178 # 2179 # Fully aromatic rings in fused ring sets might contain fully aromatic fused subsets. These 2180 # fused subets need to be tracked and treated as fused sets. 2181 # 2182 # Note: 2183 # . Fused ring sets share at least one common bond, which could be used to identify 2184 # any multiple fully aromatic fused rings sets; absence of a shared ring bond implies 2185 # there are no fused ring sets. 2186 # 2187 # 2188 sub _GetFullyAromaticFusedAndNonFusedRingsInFusedSubset { 2189 my($This, $PotentialFullyAromaticFusedRingsRef) = @_; 2190 my($RingIndex, $RingIndex1, $RingIndex2, $RingAtom, $RingAtomID, $RingIsFuesd, $RingIndicesGraph, $FusedRingSetIndicesRef, @RingIndices, @FusedRingPairIndices, @FusedRingSetIndicesRefs, @FullyAromaticFusedRingSets, @FullyAromaticRings, %RingIndexToAtomIDMap, %FullyAromaticFusedRingIndexMap); 2191 2192 @FullyAromaticFusedRingSets = (); 2193 @FullyAromaticRings = (); 2194 2195 # Setup a ring index map for ring atoms... 2196 # 2197 %RingIndexToAtomIDMap = (); 2198 for $RingIndex (0 .. $#{$PotentialFullyAromaticFusedRingsRef}) { 2199 %{$RingIndexToAtomIDMap{$RingIndex}} = (); 2200 for $RingAtom (@{$PotentialFullyAromaticFusedRingsRef->[$RingIndex]}) { 2201 $RingAtomID = $RingAtom->GetID(); 2202 $RingIndexToAtomIDMap{$RingIndex}{$RingAtomID} = $RingAtomID; 2203 } 2204 } 2205 2206 # Identify fused ring pairs... 2207 # 2208 @RingIndices = (); 2209 @FusedRingPairIndices = (); 2210 2211 for $RingIndex1 (0 .. $#{$PotentialFullyAromaticFusedRingsRef}) { 2212 push @RingIndices, $RingIndex1; 2213 for $RingIndex2 (($RingIndex1 + 1) .. $#{$PotentialFullyAromaticFusedRingsRef}) { 2214 $RingIsFuesd = 0; 2215 RINGATOM: for $RingAtom (@{$PotentialFullyAromaticFusedRingsRef->[$RingIndex2]}) { 2216 $RingAtomID = $RingAtom->GetID(); 2217 if (exists $RingIndexToAtomIDMap{$RingIndex1}{$RingAtomID}) { 2218 $RingIsFuesd = 1; 2219 last RINGATOM; 2220 } 2221 } 2222 if ($RingIsFuesd) { 2223 push @FusedRingPairIndices, ($RingIndex1, $RingIndex2); 2224 } 2225 } 2226 } 2227 2228 if (!@FusedRingPairIndices) { 2229 # No fused ring subset out there... 2230 push @FullyAromaticRings, @{$PotentialFullyAromaticFusedRingsRef}; 2231 2232 return (\@FullyAromaticFusedRingSets, \@FullyAromaticRings); 2233 } 2234 2235 # Identify fused ring sets... 2236 # 2237 $RingIndicesGraph = new Graph(@RingIndices); 2238 $RingIndicesGraph->AddEdges(@FusedRingPairIndices); 2239 @FusedRingSetIndicesRefs = $RingIndicesGraph->GetConnectedComponentsVertices(); 2240 2241 # Collect fully aromatic fused ring sets... 2242 # 2243 %FullyAromaticFusedRingIndexMap = (); 2244 for $FusedRingSetIndicesRef (@FusedRingSetIndicesRefs) { 2245 my(@FullyAromaticFusedRingSet) = (); 2246 for $RingIndex (@{$FusedRingSetIndicesRef}) { 2247 $FullyAromaticFusedRingIndexMap{$RingIndex} = $RingIndex; 2248 push @FullyAromaticFusedRingSet, $PotentialFullyAromaticFusedRingsRef->[$RingIndex]; 2249 } 2250 if (@FullyAromaticFusedRingSet) { 2251 # Sort rings by size with in the fused ring set... 2252 @FullyAromaticFusedRingSet = sort { scalar @$a <=> scalar @$b } @FullyAromaticFusedRingSet; 2253 push @FullyAromaticFusedRingSets, \@FullyAromaticFusedRingSet; 2254 } 2255 } 2256 2257 # Collect fully aromatic non-fused rings... 2258 # 2259 RINGINDEX: for $RingIndex (0 .. $#{$PotentialFullyAromaticFusedRingsRef}) { 2260 if (exists $FullyAromaticFusedRingIndexMap{$RingIndex}) { 2261 next RINGINDEX; 2262 } 2263 push @FullyAromaticRings, $PotentialFullyAromaticFusedRingsRef->[$RingIndex]; 2264 } 2265 2266 return (\@FullyAromaticFusedRingSets, \@FullyAromaticRings); 2267 } 2268 2269 # Identify individual non-fused rings containing aromatic atoms... 2270 # 2271 sub _GetIndividualRingsContainingAromaticAtoms { 2272 my($This) = @_; 2273 2274 return $This->_GetRingsContainingAromaticAtoms($This->GetRings()); 2275 } 2276 2277 # Identify individual non-fused rings containing aromatic atoms... 2278 # 2279 sub _GetRingsContainingAromaticAtoms { 2280 my($This, @Rings) = @_; 2281 my($RingAtom, $RingAtomsRef, $RingAtomsCount, $AromaticAtomsCount, $PartiallyAromaticRingComponentsRef, @FullyAromaticRings, @PartiallyAromaticRings); 2282 2283 @FullyAromaticRings = (); 2284 @PartiallyAromaticRings = (); 2285 2286 RING: for $RingAtomsRef (@Rings) { 2287 $RingAtomsCount = scalar @{$RingAtomsRef}; 2288 $AromaticAtomsCount = 0; 2289 2290 for $RingAtom (@{$RingAtomsRef}) { 2291 if ($RingAtom->IsAromatic()) { 2292 $AromaticAtomsCount += 1; 2293 } 2294 } 2295 2296 if (!($AromaticAtomsCount && $RingAtomsCount)) { 2297 next RING; 2298 } 2299 2300 if ($AromaticAtomsCount == $RingAtomsCount) { 2301 push @FullyAromaticRings, $RingAtomsRef; 2302 } 2303 else { 2304 push @PartiallyAromaticRings, $RingAtomsRef; 2305 } 2306 } 2307 2308 $PartiallyAromaticRingComponentsRef = $This->_GetPartiallyAromaticRingComponents(\@PartiallyAromaticRings); 2309 2310 return (\@FullyAromaticRings, $PartiallyAromaticRingComponentsRef); 2311 } 2312 2313 # Get connected aromatic components with in partially aromatic rings... 2314 # 2315 sub _GetPartiallyAromaticRingComponents { 2316 my($This, $PotentialPartiallyAromaticRingsRef, $FullyAromaticRingsRef) = @_; 2317 my($RingAtomsRef, $RingAtom, $RingAtomID, $Index, @PartiallyAromaticRingComponents, %FullyAromaticRingAtomsMap); 2318 2319 @PartiallyAromaticRingComponents = (); 2320 2321 # Setup a map for atoms involve in fully aromatic rings to remove remove partial rings 2322 # containing only those atoms which are already part of some other fully aromatic ring 2323 # in fused ring scenarios or some other partially aromatic ring... 2324 # 2325 %FullyAromaticRingAtomsMap = (); 2326 if (defined $FullyAromaticRingsRef) { 2327 for $RingAtomsRef (@{$FullyAromaticRingsRef}) { 2328 for $RingAtom (@{$RingAtomsRef}) { 2329 $RingAtomID = $RingAtom->GetID(); 2330 $FullyAromaticRingAtomsMap{$RingAtomID} = $RingAtomID; 2331 } 2332 } 2333 } 2334 2335 # . Identify any connected components with in each partially aromatic ring. 2336 # . Use ring atom indices to figure out connnected components in rings: All ring atoms 2337 # in a connected component have sequential indices and a difference by more than 2338 # 1 indicates a new component in the list. 2339 # 2340 RING: for $RingAtomsRef (@{$PotentialPartiallyAromaticRingsRef}) { 2341 my(@AromaticRingAtoms, @AromaticRingAtomsIndices); 2342 2343 @AromaticRingAtoms = (); 2344 @AromaticRingAtomsIndices = (); 2345 2346 RINGATOM: for $Index (0 .. $#{$RingAtomsRef}) { 2347 $RingAtom = $RingAtomsRef->[$Index]; 2348 $RingAtomID = $RingAtom->GetID(); 2349 2350 if (defined $FullyAromaticRingsRef && exists $FullyAromaticRingAtomsMap{$RingAtomID}) { 2351 next RINGATOM; 2352 } 2353 if (!$RingAtom->IsAromatic()) { 2354 next RINGATOM; 2355 } 2356 push @AromaticRingAtoms, $RingAtom; 2357 push @AromaticRingAtomsIndices, $Index; 2358 2359 } 2360 if (!@AromaticRingAtoms) { 2361 next RING; 2362 } 2363 2364 # Start off with a new connected component... 2365 # 2366 my($ComponentNum); 2367 $ComponentNum = scalar @PartiallyAromaticRingComponents; 2368 @{$PartiallyAromaticRingComponents[$ComponentNum]} = (); 2369 2370 $Index = 0; 2371 push @{$PartiallyAromaticRingComponents[$ComponentNum]}, $AromaticRingAtoms[$Index]; 2372 2373 for $Index (1 .. $#AromaticRingAtoms) { 2374 if (($AromaticRingAtomsIndices[$Index] - $AromaticRingAtomsIndices[$Index -1]) > 1) { 2375 # New connected component... 2376 $ComponentNum += 1; 2377 @{$PartiallyAromaticRingComponents[$ComponentNum]} = (); 2378 } 2379 push @{$PartiallyAromaticRingComponents[$ComponentNum]}, $AromaticRingAtoms[$Index]; 2380 } 2381 } 2382 2383 return (\@PartiallyAromaticRingComponents); 2384 } 2385 2386 # Kekulize fully aromatic fused and individual rings along with any partially aromatic ring 2387 # components... 2388 # 2389 sub _KekulizeCompleteAndPartialAromaticRings { 2390 my($This, $AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = @_; 2391 my($Status, $ConnectedPathsAtomsSetsRef, $ConnectedPathsBondsSetsRef, $ConnectdPathsSetsTypesRef, $PathSetIndex, $PathAtom, $AtomID, $BondID, $PathBondsRef, $DeleteAtomsAromaticity, $DeleteBondsAromaticity, %PathAtomsProcessingStatusMap, %PathBondsProcessingStatusMap); 2392 2393 ($ConnectedPathsAtomsSetsRef, $ConnectedPathsBondsSetsRef, $ConnectdPathsSetsTypesRef) = $This->_SetupCompleteAndPartialAromaticRingsForKekulizaiton($AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef); 2394 2395 if (!@{$ConnectedPathsAtomsSetsRef}) { 2396 # Nothing to do... 2397 return 1; 2398 } 2399 2400 # Delete any aromaticity property set for non-ring bonds connected any two ring 2401 # aromatic atoms... 2402 # 2403 $This->_ProcessNonRingAromaticBondsBetweenAromaticRingAtoms(); 2404 2405 %PathAtomsProcessingStatusMap = (); 2406 %PathBondsProcessingStatusMap = (); 2407 2408 $Status = 1; 2409 2410 PATHSET: for $PathSetIndex (0 .. $#{$ConnectedPathsAtomsSetsRef}) { 2411 my($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathSetProcessingStatusRef); 2412 2413 ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathSetProcessingStatusRef) = (undef) x 3; 2414 2415 if ($ConnectdPathsSetsTypesRef->[$PathSetIndex] =~ /^FusedAromatic$/i) { 2416 # Fused set of connected paths... 2417 # 2418 my($FusedConnectedPathAtomsSetRef, $FusedConnectedPathBondsSetRef); 2419 2420 $FusedConnectedPathAtomsSetRef = $ConnectedPathsAtomsSetsRef->[$PathSetIndex]; 2421 $FusedConnectedPathBondsSetRef = $ConnectedPathsBondsSetsRef->[$PathSetIndex]; 2422 2423 # Prepare for kekulization... 2424 ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathSetProcessingStatusRef) = $This->_SetupConnectedPathSetsForKekulization($FusedConnectedPathAtomsSetRef, $FusedConnectedPathBondsSetRef); 2425 2426 # Perform kekulization starting with the first path set... 2427 $PathSetProcessingStatusRef->[0] = 'Processed'; 2428 if (!$This->_KekulizeConnectedPathSets($FusedConnectedPathAtomsSetRef->[0], $FusedConnectedPathBondsSetRef->[0], $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $FusedConnectedPathAtomsSetRef, $FusedConnectedPathBondsSetRef, $PathSetProcessingStatusRef)) { 2429 # Kekulization failed for the current fused paths set... 2430 $Status = 0; 2431 } 2432 } 2433 else { 2434 # An individual connected path... 2435 # 2436 my(@ConnectedPathAtomsSet, @ConnectedPathBondsSet); 2437 2438 @ConnectedPathAtomsSet = ($ConnectedPathsAtomsSetsRef->[$PathSetIndex]); 2439 @ConnectedPathBondsSet = ($ConnectedPathsBondsSetsRef->[$PathSetIndex]); 2440 2441 # Prepare for kekulization... 2442 ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = $This->_SetupConnectedPathSetsForKekulization(\@ConnectedPathAtomsSet, \@ConnectedPathBondsSet); 2443 2444 # Perform kekulization... 2445 if (!$This->_KekulizeConnectedPathSets($ConnectedPathsAtomsSetsRef->[$PathSetIndex], $ConnectedPathsBondsSetsRef->[$PathSetIndex], $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef)) { 2446 # Kekulization failed for the current path... 2447 $Status = 0; 2448 } 2449 } 2450 2451 # Did kekulization succeed for the current path or path set? 2452 if (!$Status) { 2453 last PATHSET; 2454 } 2455 2456 # Track atom and bond processing state for final assignment after kekulization 2457 # is successfully completed for all the paths and fused path sets... 2458 # 2459 for $AtomID (keys %{$AtomProcessingStatusMapRef}) { 2460 $PathAtomsProcessingStatusMap{$AtomID} = $AtomProcessingStatusMapRef->{$AtomID}; 2461 } 2462 2463 for $BondID (keys %{$BondProcessingStatusMapRef}) { 2464 $PathBondsProcessingStatusMap{$BondID} = $BondProcessingStatusMapRef->{$BondID}; 2465 } 2466 } 2467 2468 if (!$Status) { 2469 carp "Warning: ${ClassName}->_KekulizeCompleteAndPartialAromaticRings: Couldn't perform kekulization for marked ring aromatic atoms..."; 2470 return 0; 2471 } 2472 2473 # Use PathAtomsProcessingStatusMap and PathBondsProcessingStatusMap to set 2474 # single/double bonds in the molecule after successful kekulization along with modification of 2475 # any aromatic flags... 2476 2477 for $PathSetIndex (0 .. $#{$ConnectedPathsAtomsSetsRef}) { 2478 $DeleteAtomsAromaticity = 0; $DeleteBondsAromaticity = 0; 2479 2480 if ($ConnectdPathsSetsTypesRef->[$PathSetIndex] =~ /^FusedAromatic$/i) { 2481 for $PathBondsRef (@{$ConnectedPathsBondsSetsRef->[$PathSetIndex]}) { 2482 $This->_ProcessBondOrdersAssignedDuringSuccessfulKekulization($PathBondsRef, \%PathBondsProcessingStatusMap, $DeleteBondsAromaticity); 2483 } 2484 } 2485 else { 2486 if ($ConnectdPathsSetsTypesRef->[$PathSetIndex] =~ /^PartiallyAromatic$/i ) { 2487 $DeleteBondsAromaticity = 1; $DeleteAtomsAromaticity = 1; 2488 } 2489 2490 if ($DeleteAtomsAromaticity) { 2491 for $PathAtom (@{$ConnectedPathsAtomsSetsRef->[$PathSetIndex]}) { 2492 $PathAtom->DeleteAromatic(); 2493 } 2494 } 2495 2496 $This->_ProcessBondOrdersAssignedDuringSuccessfulKekulization($ConnectedPathsBondsSetsRef->[$PathSetIndex], \%PathBondsProcessingStatusMap, $DeleteBondsAromaticity); 2497 } 2498 } 2499 2500 return 1; 2501 } 2502 2503 # Look for any aromatic bonds outside the rings between two ring aromatic atoms 2504 # and turn them into single non-aromatic bonds before kekulization; otherwise, kekulization 2505 # fails. 2506 # 2507 # Note: 2508 # . Two atoms marked as aromatic atoms in two different rings, such as two rings 2509 # connected through a single bond, are still aromatic, but the bond is outside 2510 # the ring and shouldn't be marked as aromatic. It should be set to single bond without 2511 # any aromatic property for kekulization to succeed. 2512 # 2513 # For example, the molecule generated by SMILES parser for biphenyl SMILES string 2514 # "c1ccccc1c2ccccc2" sets up an aromatic bond between the two phenyl rings, as 2515 # it's connected to two aromatic atoms. 2516 # 2517 sub _ProcessNonRingAromaticBondsBetweenAromaticRingAtoms { 2518 my($This) = @_; 2519 my($Bond, $Atom1, $Atom2); 2520 2521 BOND: for $Bond ($This->GetBonds()) { 2522 if (!($Bond->IsAromatic() && $Bond->IsNotInRing())) { 2523 next BOND; 2524 } 2525 2526 ($Atom1, $Atom2) = $Bond->GetAtoms(); 2527 if (!($Atom1->IsAromatic() && $Atom2->IsAromatic() && $Atom1->IsInRing() && $Atom2->IsInRing())) { 2528 next BOND; 2529 } 2530 2531 $Bond->SetBondOrder(1); 2532 $Bond->DeleteAromatic(); 2533 } 2534 2535 return $This; 2536 } 2537 2538 # Setup completelty aromatic fused and individual rings along with partially aromatic ring 2539 # components as sets of connected paths... 2540 # 2541 sub _SetupCompleteAndPartialAromaticRingsForKekulizaiton { 2542 my($This, $AromaticFusedRingSetsRef, $AromaticRingsRef, $PartiallyAromaticRingComponentsRef) = @_; 2543 my(@ConnectedPathsSets, @ConnectedPathsBondsSets, @ConnectdPathsSetsTypes); 2544 2545 @ConnectedPathsSets = (); 2546 @ConnectedPathsBondsSets = (); 2547 @ConnectdPathsSetsTypes = (); 2548 2549 # Setup atoms and bonds for connected paths in fused aromatic ring sets... 2550 # 2551 if (defined $AromaticFusedRingSetsRef && @{$AromaticFusedRingSetsRef}) { 2552 my($RingSetIndex); 2553 2554 push @ConnectdPathsSetsTypes, ('FusedAromatic') x scalar @{$AromaticFusedRingSetsRef}; 2555 push @ConnectedPathsSets, @{$AromaticFusedRingSetsRef}; 2556 2557 for $RingSetIndex (0 .. $#{$AromaticFusedRingSetsRef}) { 2558 my(@AromaticFusedRingBondsSet); 2559 2560 # Get ring bonds for each ring set... 2561 # 2562 @AromaticFusedRingBondsSet = $This->GetRingBondsFromRings(@{$AromaticFusedRingSetsRef->[$RingSetIndex]}); 2563 push @ConnectedPathsBondsSets, \@AromaticFusedRingBondsSet; 2564 } 2565 } 2566 2567 # Set up atoms and bonds for connected paths in aromatic rings... 2568 # 2569 if (defined $AromaticRingsRef && @{$AromaticRingsRef}) { 2570 my(@AromaticRingBondsSets); 2571 2572 push @ConnectdPathsSetsTypes, ('Aromatic') x scalar @{$AromaticRingsRef}; 2573 push @ConnectedPathsSets, @{$AromaticRingsRef}; 2574 2575 # Get ring bonds for each ring... 2576 @AromaticRingBondsSets = $This->GetRingBondsFromRings(@{$AromaticRingsRef}); 2577 push @ConnectedPathsBondsSets, @AromaticRingBondsSets; 2578 } 2579 2580 # Set up atoms and bonds for connected paths in partially aromatic rings... 2581 # 2582 if (defined $PartiallyAromaticRingComponentsRef && @{$PartiallyAromaticRingComponentsRef}) { 2583 my($ComponentIndex); 2584 2585 push @ConnectedPathsSets, @{$PartiallyAromaticRingComponentsRef}; 2586 push @ConnectdPathsSetsTypes, ('PartiallyAromatic') x scalar @{$PartiallyAromaticRingComponentsRef}; 2587 2588 for $ComponentIndex (0 .. $#{$PartiallyAromaticRingComponentsRef}) { 2589 my(@ComponentBonds); 2590 @ComponentBonds = $This->_GetPathBonds($This->_GetAtomsIDsFromAtoms(@{$PartiallyAromaticRingComponentsRef->[$ComponentIndex]})); 2591 push @ConnectedPathsBondsSets, \@ComponentBonds; 2592 } 2593 } 2594 2595 return (\@ConnectedPathsSets, \@ConnectedPathsBondsSets, \@ConnectdPathsSetsTypes); 2596 } 2597 2598 # Process non-ring connected atoms which are marked aromatic and set connected 2599 # bonds as alternate single/double bonds... 2600 # 2601 # Notes: 2602 # . Atom and bond aromaticity is deleted during kekulization of non-ring atoms. 2603 # 2604 sub _KekulizeAromaticAtomsNotInRings { 2605 my($This) = @_; 2606 my($Status, $PathIndex, $PathAtom, $PathAtomID, $PathBondID, $ConnectedPathsAtomsRef, $ConnectedPathsBondsRef, $DeleteAtomsAromaticity, $DeleteBondsAromaticity, %PathAtomsProcessingStatusMap, %PathBondsProcessingStatusMap); 2607 2608 if (!$This->HasAromaticAtomsNotInRings()) { 2609 # Nothing to do... 2610 return 1; 2611 } 2612 2613 # Identify paths for connected components containing non-ring aromatic atoms... 2614 ($ConnectedPathsAtomsRef, $ConnectedPathsBondsRef) = $This->_GetConnectedComponentsPathsForNonRingAromaticAtoms(); 2615 2616 if (!@{$ConnectedPathsAtomsRef}) { 2617 carp "Warning: ${ClassName}->_KekulizeAromaticAtomsNotInRings: Couldn't perform kekulization for marked non-ring aromatic atoms..."; 2618 return 0; 2619 } 2620 2621 %PathAtomsProcessingStatusMap = (); 2622 %PathBondsProcessingStatusMap = (); 2623 2624 $Status = 1; 2625 2626 PATH: for $PathIndex (0 .. $#{$ConnectedPathsAtomsRef}) { 2627 my($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, @ConnectedPathAtomsSet, @ConnectedPathBondsSet); 2628 2629 ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = (undef) x 2; 2630 2631 @ConnectedPathAtomsSet = ($ConnectedPathsAtomsRef->[$PathIndex]); 2632 @ConnectedPathBondsSet = ($ConnectedPathsBondsRef->[$PathIndex]); 2633 2634 # Prepare for kekulization... 2635 ($AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = $This->_SetupConnectedPathSetsForKekulization(\@ConnectedPathAtomsSet, \@ConnectedPathBondsSet); 2636 2637 # Perform kekulization... 2638 if (!$This->_KekulizeConnectedPathSets($ConnectedPathsAtomsRef->[$PathIndex], $ConnectedPathsBondsRef->[$PathIndex], $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef)) { 2639 # Kekulization failed for the current path... 2640 $Status = 0; 2641 last PATH; 2642 } 2643 2644 # Track atom and bond processing state for final assignment after kekulization 2645 # is successfully completed for all the paths and fused path sets... 2646 # 2647 for $PathAtomID (keys %{$AtomProcessingStatusMapRef}) { 2648 $PathAtomsProcessingStatusMap{$PathAtomID} = $AtomProcessingStatusMapRef->{$PathAtomID}; 2649 } 2650 2651 for $PathBondID (keys %{$BondProcessingStatusMapRef}) { 2652 $PathBondsProcessingStatusMap{$PathBondID} = $BondProcessingStatusMapRef->{$PathBondID}; 2653 } 2654 } 2655 2656 if (!$Status) { 2657 carp "Warning: ${ClassName}->_KekulizeAromaticAtomsNotInRings: Couldn't perform kekulization for marked non-ring aromatic atoms..."; 2658 return 0; 2659 } 2660 2661 $DeleteAtomsAromaticity = 1; $DeleteBondsAromaticity = 1; 2662 for $PathIndex (0 .. $#{$ConnectedPathsAtomsRef}) { 2663 if ($DeleteAtomsAromaticity) { 2664 for $PathAtom (@{$ConnectedPathsAtomsRef->[$PathIndex]}) { 2665 $PathAtom->DeleteAromatic(); 2666 } 2667 } 2668 $This->_ProcessBondOrdersAssignedDuringSuccessfulKekulization($ConnectedPathsBondsRef->[$PathIndex], \%PathBondsProcessingStatusMap, $DeleteBondsAromaticity); 2669 } 2670 2671 return 1; 2672 } 2673 2674 # Collect path atoms for connected components paths containing non-ring aromatic atoms... 2675 # 2676 sub _GetConnectedComponentsPathsForNonRingAromaticAtoms { 2677 my($This) = @_; 2678 my($ComponentRef, $AtomIDsRef, $AtomIDsMapRef, $ConnectedComponentsAtomIDsRef, $ConnectedComponentsAtomIDsMapRef, $ConnectedComponentsPathsAtomIDsRef, $ConnectedComponentsPathsAtomsRef, $ConnectedComponentsPathsBondsRef); 2679 2680 # Retrieve information for marked aromatic atoms not in the rings... 2681 ($AtomIDsRef, $AtomIDsMapRef) = $This->_GetNonRingAromaticAtomIDs(); 2682 2683 # Identify connected components containing marked aromatic atoms not in the rings... 2684 ($ConnectedComponentsAtomIDsRef, $ConnectedComponentsAtomIDsMapRef) = $This->_GetConnectedComponentsForNonRingAromaticAtoms($AtomIDsRef); 2685 2686 # Identify paths for connected components containing non-ring aromatic atoms... 2687 ($ConnectedComponentsPathsAtomsRef, $ConnectedComponentsPathsBondsRef) = $This->_GetConnectedComponentsPathsAtomsAndBondsForNonRingAromaticAtoms($AtomIDsMapRef, $ConnectedComponentsAtomIDsRef, $ConnectedComponentsAtomIDsMapRef); 2688 2689 return ($ConnectedComponentsPathsAtomsRef, $ConnectedComponentsPathsBondsRef); 2690 } 2691 2692 # Collect information for marked aromatic atoms not in the rings... 2693 # 2694 sub _GetNonRingAromaticAtomIDs { 2695 my($This) = @_; 2696 my($Atom, $AtomID, @AtomIDs, %AtomIDsMap); 2697 2698 @AtomIDs = (); 2699 %AtomIDsMap = (); 2700 2701 ATOM: for $Atom ($This->GetAtoms()) { 2702 if (!$Atom->IsAromatic()) { 2703 next ATOM; 2704 } 2705 if ($Atom->IsInRing()) { 2706 next ATOM; 2707 } 2708 $AtomID = $Atom->GetID(); 2709 2710 push @AtomIDs, $AtomID; 2711 $AtomIDsMap{$AtomID} = $Atom; 2712 } 2713 2714 return (\@AtomIDs, \%AtomIDsMap); 2715 } 2716 2717 # Retrieve connected non-ring atom components as a reference to an array of references 2718 # containing atom IDs of connecnted components... 2719 # 2720 sub _GetConnectedComponentsForNonRingAromaticAtoms { 2721 my($This, $AtomIDsRef) = @_; 2722 my($Index, $AtomID, $AtomIDsGraph, @BondedAtomPairIDs, @ComponentsAtomIDsRefs, @ComponentsAtomIDsMapRefs); 2723 2724 @ComponentsAtomIDsRefs = (); 2725 @ComponentsAtomIDsMapRefs = (); 2726 2727 # Get bonded atom pair IDs... 2728 @BondedAtomPairIDs = $This->_GetBondedAtomPairAtomIDsFromAtomIDs(@{$AtomIDsRef}); 2729 2730 if (!@BondedAtomPairIDs) { 2731 return (\@ComponentsAtomIDsRefs, \@ComponentsAtomIDsMapRefs); 2732 } 2733 2734 $AtomIDsGraph = new Graph(@{$AtomIDsRef}); 2735 $AtomIDsGraph->AddEdges(@BondedAtomPairIDs); 2736 2737 @ComponentsAtomIDsRefs = $AtomIDsGraph->GetConnectedComponentsVertices(); 2738 2739 # Setup atom IDs map for each component... 2740 for $Index (0 .. $#ComponentsAtomIDsRefs) { 2741 %{$ComponentsAtomIDsMapRefs[$Index]} = (); 2742 2743 for $AtomID (@{$ComponentsAtomIDsRefs[$Index]}) { 2744 $ComponentsAtomIDsMapRefs[$Index]{$AtomID} = $AtomID; 2745 } 2746 } 2747 2748 return (\@ComponentsAtomIDsRefs, \@ComponentsAtomIDsMapRefs); 2749 } 2750 2751 # Get linear paths for connected components starting and ending at terminal aromatic atoms, 2752 # which are connected to only one other aromatic atom in the connected component.. 2753 # 2754 sub _GetConnectedComponentsPathsAtomsAndBondsForNonRingAromaticAtoms { 2755 my($This, $AtomIDsMapRef, $ComponentsAtomIDsRef, $ComponentsAtomIDsMapRef) = @_; 2756 my($Index, $AtomID, $Atom, $AtomNbr, $AtomNbrID, $NumOfNonRingAromaticNbrs, $AtomIndex1, $AtomIndex2, $AtomID1, $AtomID2, $Atom1, $Atom2, $AtomIDsGraph, $StartTerminalAtomID, $EndTerminalAtomID, @Paths, @PathAtomIDs, @PathsAtoms, @PathsBonds, @TerminalAtomIDs, @AtomIDs, @BondedAtomPairIDs); 2757 2758 @PathsAtoms = (); 2759 @PathsBonds = (); 2760 2761 @TerminalAtomIDs = (); 2762 2763 $Index = 0; 2764 COMPONENT: for $Index (0 .. $#{$ComponentsAtomIDsRef}) { 2765 @{$TerminalAtomIDs[$Index]} = (); 2766 2767 # Identify terminal atoms for connected components... 2768 # 2769 # Notes: 2770 # . Terminal atoms are defined as atoms connected to only one marked 2771 # aromatic atom. 2772 # . Linear connected compoents contain only two terminal atoms. 2773 # 2774 ATOM: for $AtomID (@{$ComponentsAtomIDsRef->[$Index]}) { 2775 $Atom = $AtomIDsMapRef->{$AtomID}; 2776 $NumOfNonRingAromaticNbrs = 0; 2777 2778 ATOMNBRID: for $AtomNbr ($Atom->GetNeighbors()) { 2779 $AtomNbrID = $AtomNbr->GetID(); 2780 2781 # Is neighbor in the same connected components containing aromatic atoms? 2782 if (!exists $ComponentsAtomIDsMapRef->[$Index]{$AtomNbrID}) { 2783 next ATOMNBRID; 2784 } 2785 $NumOfNonRingAromaticNbrs++; 2786 } 2787 2788 # Is it a terminal atom? 2789 if ($NumOfNonRingAromaticNbrs != 1) { 2790 next ATOM; 2791 } 2792 push @{$TerminalAtomIDs[$Index]}, $AtomID; 2793 } 2794 2795 if (@{$TerminalAtomIDs[$Index]} != 2) { 2796 next COMPONENT; 2797 } 2798 2799 # Setup bonded atom pair IDs for connected component... 2800 # 2801 @AtomIDs = @{$ComponentsAtomIDsRef->[$Index]}; 2802 @BondedAtomPairIDs = (); 2803 2804 for $AtomIndex1 ( 0 .. $#AtomIDs) { 2805 $AtomID1 = $AtomIDs[$AtomIndex1]; 2806 $Atom1 = $AtomIDsMapRef->{$AtomID1}; 2807 2808 for $AtomIndex2 ( ($AtomIndex1 + 1) .. $#AtomIDs) { 2809 $AtomID2 = $AtomIDs[$AtomIndex2]; 2810 $Atom2 = $AtomIDsMapRef->{$AtomID2}; 2811 2812 if ($Atom1->IsBondedToAtom($Atom2)) { 2813 push @BondedAtomPairIDs, ($AtomID1, $AtomID2); 2814 } 2815 } 2816 } 2817 2818 if (!@BondedAtomPairIDs) { 2819 next COMPONENT; 2820 } 2821 2822 # Get path for connected component... 2823 $AtomIDsGraph = new Graph(@AtomIDs); 2824 $AtomIDsGraph->AddEdges(@BondedAtomPairIDs); 2825 2826 ($StartTerminalAtomID, $EndTerminalAtomID) = sort { $a <=> $b } @{$TerminalAtomIDs[$Index]}; 2827 @Paths = $AtomIDsGraph->GetPathsBetween($StartTerminalAtomID, $EndTerminalAtomID); 2828 2829 if (@Paths != 1) { 2830 next COMPONENT; 2831 } 2832 2833 @PathAtomIDs = $Paths[0]->GetVertices(); 2834 2835 my(@PathAtoms); 2836 @PathAtoms = $This->_GetAtomsFromAtomIDs(@PathAtomIDs); 2837 push @PathsAtoms, \@PathAtoms; 2838 2839 my(@PathBonds); 2840 @PathBonds = $This->_GetPathBonds(@PathAtomIDs); 2841 push @PathsBonds, \@PathBonds; 2842 2843 } 2844 2845 return (\@PathsAtoms, \@PathsBonds); 2846 } 2847 2848 # Setup initial processing status of atoms and bonds involved in connected paths 2849 # before starting kekulization... 2850 # 2851 # Possible atom processing status: DoubleBondPossible, DoubleBondAssigned, DoubleBondNotPossible 2852 # Initial status: DoubleBondPossible or DoubleBondNotPossible 2853 # 2854 # Possible bond processing status: DoubleBondAssigned, SingleBondAssigned, NotProcessed 2855 # 2856 # Possible paths processing status: Processed, NotProcessed 2857 # Initial status: NotProcessed 2858 # 2859 sub _SetupConnectedPathSetsForKekulization { 2860 my($This, $PathAtomsSetsRef, $PathBondsSetsRef) = @_; 2861 my($PathIndex, $PathAtomsRef, $PathBondsRef, $Atom, $AtomID, $Bond, $BondID, %AtomProcessingStatusMap, %BondProcessingStatusMap, @PathsProcessingStatus, %InitialPathBondOrderMap); 2862 2863 # Possible path set status values: Processed, NotProcessed 2864 # Initial value: NotProcessed 2865 # 2866 @PathsProcessingStatus = ('NotProcessed') x scalar @{$PathAtomsSetsRef}; 2867 2868 # Collect initial bond order of path bonds before setting bond orders to 1 2869 # and use it to set the bond order back to intial value after it has been processed for 2870 # availability of double bonds... 2871 # 2872 %InitialPathBondOrderMap = (); 2873 for $PathBondsRef (@{$PathBondsSetsRef}) { 2874 BOND: for $Bond (@{$PathBondsRef}) { 2875 $BondID = $Bond->GetID(); 2876 if (exists $InitialPathBondOrderMap{$BondID}) { 2877 next BOND; 2878 } 2879 $InitialPathBondOrderMap{$BondID} = $Bond->GetBondOrder(); 2880 $Bond->SetBondOrder(1); 2881 } 2882 } 2883 2884 %AtomProcessingStatusMap = (); 2885 %BondProcessingStatusMap = (); 2886 2887 for $PathIndex (0 .. $#{$PathAtomsSetsRef}) { 2888 2889 $PathAtomsRef = $PathAtomsSetsRef->[$PathIndex]; 2890 ATOM: for $Atom (@{$PathAtomsRef}) { 2891 $AtomID = $Atom->GetID(); 2892 if (exists $AtomProcessingStatusMap{$AtomID}) { 2893 next ATOM; 2894 } 2895 $AtomProcessingStatusMap{$AtomID} = ($Atom->GetNumOfBondsAvailableForNonHydrogenAtoms() >= 1) ? 'DoubleBondPossible' : 'DoubleBondNotPossible'; 2896 } 2897 2898 $PathBondsRef = $PathBondsSetsRef->[$PathIndex]; 2899 BOND: for $Bond (@{$PathBondsRef}) { 2900 $BondID = $Bond->GetID(); 2901 if (exists $BondProcessingStatusMap{$BondID}) { 2902 next BOND; 2903 } 2904 $BondProcessingStatusMap{$BondID} = 'NotProcessed'; 2905 } 2906 } 2907 2908 # Set bond orders back to initial bond orders... 2909 for $PathIndex (0 .. $#{$PathAtomsSetsRef}) { 2910 $PathBondsRef = $PathBondsSetsRef->[$PathIndex]; 2911 2912 for $Bond (@{$PathBondsRef}) { 2913 $BondID = $Bond->GetID(); 2914 if (exists $InitialPathBondOrderMap{$BondID}) { 2915 $Bond->SetBondOrder($InitialPathBondOrderMap{$BondID}); 2916 } 2917 } 2918 } 2919 2920 return (\%AtomProcessingStatusMap, \%BondProcessingStatusMap, \@PathsProcessingStatus); 2921 } 2922 2923 # Kekulize connected path sets corresponding to fused rings, individual rings, or any other 2924 # connected path... 2925 # 2926 # Note: 2927 # . PathAtomsRef and PathBondsRef contain paths and bonds corresponding to path 2928 # under consideration for kekulization 2929 # . PathAtomsSetsRef and PathBondsSetsRef contain any other available paths fused 2930 # to the path being kekulized 2931 # . _KekulizeConnectedPathSets is invoked recursively to kekulize all available paths 2932 # 2933 sub _KekulizeConnectedPathSets { 2934 my($This, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef) = @_; 2935 my($PathBond); 2936 2937 # Get next available path bond... 2938 $PathBond = $This->_GetNextAvailablePathBondForKekulization($PathBondsRef, $BondProcessingStatusMapRef); 2939 2940 if ($PathBond) { 2941 return $This->_ProcessNextAvailablePathBondForKekulization($PathBond, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef); 2942 } 2943 2944 # Did kekulization succeed for the current path bonds? 2945 if (!$This->_DidKekulizationSucceedForPathBonds($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef)) { 2946 return 0; 2947 } 2948 2949 # Is there any other path available for kekulization? 2950 ($PathAtomsRef, $PathBondsRef) = $This->_GetNextAvailablePathForKekulization($PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef); 2951 2952 if ($PathAtomsRef && $PathBondsRef) { 2953 # Recursively call itself to kekulize next path, which could either be a new path or part 2954 # of a fused paths corresponding to fused ring sets... 2955 # 2956 return $This->_KekulizeConnectedPathSets($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef); 2957 } 2958 2959 return 1; 2960 } 2961 2962 # Get next available path bond in a list of path bonds... 2963 # 2964 sub _GetNextAvailablePathBondForKekulization { 2965 my($This, $PathBondsRef, $BondProcessingStatusMapRef) = @_; 2966 my($AvailablePathBond, $PathBond, $PathBondID); 2967 2968 $AvailablePathBond = undef; 2969 2970 BOND: for $PathBond (@{$PathBondsRef}) { 2971 $PathBondID = $PathBond->GetID(); 2972 if (!exists $BondProcessingStatusMapRef->{$PathBondID}) { 2973 next BOND; 2974 } 2975 if ($BondProcessingStatusMapRef->{$PathBondID} =~ /^NotProcessed$/i) { 2976 $AvailablePathBond = $PathBond; 2977 last BOND; 2978 } 2979 } 2980 2981 return ($AvailablePathBond); 2982 } 2983 2984 # Process next available path bond for kekulizaiton... 2985 # 2986 sub _ProcessNextAvailablePathBondForKekulization { 2987 my($This, $PathBond, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef) = @_; 2988 my($PathBondID, $PathAtom1, $PathAtom2, $PathAtomID1, $PathAtomID2, %CurrentAtomProcessingStatusMap, %CurrentBondProcessingStatusMap); 2989 2990 $PathBondID = $PathBond->GetID(); 2991 2992 ($PathAtom1, $PathAtom2) = $PathBond->GetAtoms(); 2993 ($PathAtomID1, $PathAtomID2) = ($PathAtom1->GetID(), $PathAtom2->GetID()); 2994 2995 %CurrentAtomProcessingStatusMap = %{$AtomProcessingStatusMapRef}; 2996 %CurrentBondProcessingStatusMap = %{$BondProcessingStatusMapRef}; 2997 2998 # Is it possible to assign a double bond to the current path bond? 2999 if ($AtomProcessingStatusMapRef->{$PathAtomID1} =~ /^DoubleBondPossible$/i && $AtomProcessingStatusMapRef->{$PathAtomID2} =~ /^DoubleBondPossible$/i ) { 3000 # Set current bond to double bond by appropriately marking atom and bond process status... 3001 $AtomProcessingStatusMapRef->{$PathAtomID1} = 'DoubleBondAssigned'; 3002 $AtomProcessingStatusMapRef->{$PathAtomID2} = 'DoubleBondAssigned'; 3003 3004 $BondProcessingStatusMapRef->{$PathBondID} = 'DoubleBondAssigned'; 3005 3006 # Recursively call _KekulizeConnectedPathSets to kekulize next available bond... 3007 if ($This->_KekulizeConnectedPathSets($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef)) { 3008 return 1; 3009 } 3010 3011 # Double bond at the current ring bond position didn't lead to successful kekulization... 3012 %{$AtomProcessingStatusMapRef} = %CurrentAtomProcessingStatusMap; 3013 %{$BondProcessingStatusMapRef} = %CurrentBondProcessingStatusMap; 3014 } 3015 3016 # Try single bond at the current ring bond position and recursively call _KekulizeConnectedPathSets to kekulize 3017 # rest of the ring bonds... 3018 # 3019 $BondProcessingStatusMapRef->{$PathBondID} = 'SingleBondAssigned'; 3020 3021 if ($This->_KekulizeConnectedPathSets($PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef)) { 3022 return 1; 3023 } 3024 3025 %{$AtomProcessingStatusMapRef} = %CurrentAtomProcessingStatusMap; 3026 %{$BondProcessingStatusMapRef} = %CurrentBondProcessingStatusMap; 3027 3028 # Kekulization didn't work out for path bonds... 3029 3030 return 0; 3031 3032 } 3033 3034 # Get next available path for kekulization from a set of fused ring paths... 3035 # 3036 sub _GetNextAvailablePathForKekulization { 3037 my($This, $PathAtomsSetsRef, $PathBondsSetsRef, $PathsProcessingStatusRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = @_; 3038 my($PathIndex, $AvailablePathIndex, $PathAtomsRef, $PathBondsRef, $PathBond, $PathBondID, $MaxNumOfPathBondsProcessed, $NumOfPathBondsProcessed); 3039 3040 ($PathAtomsRef, $PathBondsRef, $AvailablePathIndex) = (undef) x 3; 3041 3042 if (!(defined($PathAtomsSetsRef) && defined($PathBondsSetsRef) && defined($PathsProcessingStatusRef))) { 3043 return ($PathAtomsRef, $PathBondsRef); 3044 } 3045 3046 $MaxNumOfPathBondsProcessed = -999; 3047 $AvailablePathIndex = undef; 3048 3049 PATHINDEX: for $PathIndex (0 .. $#{$PathsProcessingStatusRef}) { 3050 if ($PathsProcessingStatusRef->[$PathIndex] =~ /^Processed$/i) { 3051 next PATHINDEX; 3052 } 3053 3054 # Count of already processed bonds in an unprocessed path bonds through 3055 # their participation in any fused bonds sets... 3056 # 3057 $NumOfPathBondsProcessed = 0; 3058 PATHBOND: for $PathBond (@{$PathBondsSetsRef->[$PathIndex]}) { 3059 $PathBondID = $PathBond->GetID(); 3060 if ($BondProcessingStatusMapRef->{$PathBondID} =~ /^NotProcessed$/i) { 3061 next PATHBOND; 3062 } 3063 $NumOfPathBondsProcessed++; 3064 } 3065 3066 if ($NumOfPathBondsProcessed > $MaxNumOfPathBondsProcessed) { 3067 $AvailablePathIndex = $PathIndex; 3068 $MaxNumOfPathBondsProcessed = $NumOfPathBondsProcessed; 3069 } 3070 3071 } 3072 3073 # Is any path available? 3074 if (!$AvailablePathIndex) { 3075 return ($PathAtomsRef, $PathBondsRef); 3076 } 3077 3078 $PathsProcessingStatusRef->[$AvailablePathIndex] = 'Processed'; 3079 3080 $PathAtomsRef = $PathAtomsSetsRef->[$AvailablePathIndex]; 3081 $PathBondsRef = $PathBondsSetsRef->[$AvailablePathIndex]; 3082 3083 return ($PathAtomsRef, $PathBondsRef); 3084 } 3085 3086 # Check for kekulization in a specific set of path bonds. For successful kekulization, all 3087 # all path atoms marked with DoubleBondPossible must be involved in a path double bond... 3088 # 3089 sub _DidKekulizationSucceedForPathBonds { 3090 my($This, $PathAtomsRef, $PathBondsRef, $AtomProcessingStatusMapRef, $BondProcessingStatusMapRef) = @_; 3091 my($PathAtom, $PathAtomID); 3092 3093 for $PathAtom (@{$PathAtomsRef}) { 3094 $PathAtomID = $PathAtom->GetID(); 3095 if (exists $AtomProcessingStatusMapRef->{$PathAtomID} && $AtomProcessingStatusMapRef->{$PathAtomID} =~ /^DoubleBondPossible$/i) { 3096 return 0; 3097 } 3098 } 3099 return 1; 3100 } 3101 3102 # Assign bond orders to the bonds in a molecule which have been successfully 3103 # kekulized along with optional clearing of aromaticty property... 3104 # 3105 sub _ProcessBondOrdersAssignedDuringSuccessfulKekulization { 3106 my($This, $BondsRef, $BondsProcessingStatusMapRef, $DeleteBondsAromaticity) = @_; 3107 my($Bond, $BondID, $BondOrder); 3108 3109 $DeleteBondsAromaticity = defined $DeleteBondsAromaticity ? $DeleteBondsAromaticity : 0; 3110 3111 BOND: for $Bond (@{$BondsRef}) { 3112 $BondID = $Bond->GetID(); 3113 3114 if (!exists $BondsProcessingStatusMapRef->{$BondID}) { 3115 carp "Warning: ${ClassName}->_ProcessBondOrdersAssignedDuringSuccessfulKekulization: Couldn't process bond with bond ID, $BondID: It's not available in the list of bonds processed for kekulization..."; 3116 next BOND; 3117 } 3118 3119 $BondOrder = ($BondsProcessingStatusMapRef->{$BondID} =~ /^DoubleBondAssigned$/i) ? 2 : 1; 3120 $Bond->SetBondOrder($BondOrder); 3121 3122 if ($DeleteBondsAromaticity) { 3123 $Bond->DeleteAromatic(); 3124 } 3125 } 3126 return $This; 3127 } 3128 3129 # Does molecule contains aromatic rings? 3130 # 3131 sub HasAromaticRings { 3132 my($This) = @_; 3133 3134 return $This->GetNumOfAromaticRings() ? 1 : 0; 3135 } 3136 3137 # Does molecule contains any aromatic atom in a ring? 3138 # 3139 sub HasAromaticAtomsInRings { 3140 my($This) = @_; 3141 my($Atom); 3142 3143 ATOM: for $Atom ($This->GetAtoms()) { 3144 if (!$Atom->IsAromatic()) { 3145 next ATOM; 3146 } 3147 if ($Atom->IsInRing()) { 3148 return 1; 3149 } 3150 } 3151 return 0; 3152 } 3153 3154 # Does molecule contains any aromatic atom not in a ring? 3155 # 3156 sub HasAromaticAtomsNotInRings { 3157 my($This) = @_; 3158 my($Atom); 3159 3160 ATOM: for $Atom ($This->GetAtoms()) { 3161 if (!$Atom->IsAromatic()) { 3162 next ATOM; 3163 } 3164 if ($Atom->IsNotInRing()) { 3165 return 1; 3166 } 3167 } 3168 return 0; 3169 } 3170 3171 # Does molecule contains rings? 3172 # 3173 sub HasRings { 3174 my($This) = @_; 3175 3176 return $This->IsCyclic(); 3177 } 3178 3179 # Does molecule contains only one ring? 3180 # 3181 sub HasOnlyOneRing { 3182 my($This) = @_; 3183 3184 return $This->IsUnicyclic(); 3185 } 3186 3187 # Does molecule contains any rings? 3188 # 3189 sub HasNoRings { 3190 my($This) = @_; 3191 3192 return $This->IsAcyclic(); 3193 } 3194 3195 # Get size of smallest ring... 3196 # 3197 sub GetSizeOfSmallestRing { 3198 my($This) = @_; 3199 3200 return $This->GetSizeOfSmallestCycle(); 3201 } 3202 3203 # Get size of largest ring... 3204 # 3205 sub GetSizeOfLargestRing { 3206 my($This) = @_; 3207 3208 return $This->GetSizeOfLargestCycle(); 3209 } 3210 3211 # Get number of rings... 3212 # 3213 sub GetNumOfRings { 3214 my($This) = @_; 3215 3216 return $This->GetNumOfCycles(); 3217 } 3218 3219 # Get number of aromatic rings... 3220 # 3221 sub GetNumOfAromaticRings { 3222 my($This) = @_; 3223 my($NumOfRings); 3224 3225 $NumOfRings = scalar $This->GetAromaticRings(); 3226 3227 return $NumOfRings; 3228 } 3229 3230 # Get num of rings with odd size... 3231 # 3232 sub GetNumOfRingsWithOddSize { 3233 my($This) = @_; 3234 3235 return $This->GetNumOfCyclesWithOddSize(); 3236 } 3237 3238 # Get num of rings with even size... 3239 # 3240 sub GetNumOfRingsWithEvenSize { 3241 my($This) = @_; 3242 3243 return $This->GetNumOfCyclesWithEvenSize(); 3244 } 3245 3246 # Get num of rings with specified size... 3247 # 3248 sub GetNumOfRingsWithSize { 3249 my($This, $RingSize) = @_; 3250 3251 return $This->GetNumOfCyclesWithSize($RingSize); 3252 } 3253 3254 # Get num of rings with size less than a specified size... 3255 # 3256 sub GetNumOfRingsWithSizeLessThan { 3257 my($This, $RingSize) = @_; 3258 3259 return $This->GetNumOfCyclesWithSizeLessThan($RingSize); 3260 } 3261 3262 # Get num of rings with size greater than a specified size... 3263 # 3264 sub GetNumOfRingsWithSizeGreaterThan { 3265 my($This, $RingSize) = @_; 3266 3267 return $This->GetNumOfCyclesWithSizeGreaterThan($RingSize); 3268 } 3269 3270 # Get largest ring as an array containing ring atoms... 3271 # 3272 sub GetLargestRing { 3273 my($This) = @_; 3274 3275 return $This->_GetRing($This->GetLargestCycle()); 3276 } 3277 3278 # Get smallest ring as an array containing ring atoms... 3279 # 3280 sub GetSmallestRing { 3281 my($This) = @_; 3282 3283 return $This->_GetRing($This->GetSmallestCycle()); 3284 } 3285 3286 # Get rings as an array containing references to arrays with ring atoms... 3287 # 3288 sub GetRings { 3289 my($This) = @_; 3290 3291 return $This->_GetRings($This->GetCycles()); 3292 } 3293 3294 # Get aromatic rings as an array containing references to arrays with ring atoms... 3295 # 3296 sub GetAromaticRings { 3297 my($This) = @_; 3298 3299 return $This->_GetAromaticRings($This->GetCycles()); 3300 } 3301 3302 # Get odd size rings as an array containing references to arrays with ring atoms... 3303 # 3304 sub GetRingsWithOddSize { 3305 my($This) = @_; 3306 3307 return $This->_GetRings($This->GetCyclesWithOddSize()); 3308 } 3309 3310 # Get even size rings as an array containing references to arrays with ring atoms... 3311 # 3312 sub GetRingsWithEvenSize { 3313 my($This) = @_; 3314 3315 return $This->_GetRings($This->GetCyclesWithEvenSize()); 3316 } 3317 3318 # Get rings with a specific size as an array containing references to arrays with ring atoms... 3319 # 3320 sub GetRingsWithSize { 3321 my($This, $RingSize) = @_; 3322 3323 return $This->_GetRings($This->GetCyclesWithSize($RingSize)); 3324 } 3325 3326 # Get rings with size less than a specific size as an array containing references to arrays with ring atoms... 3327 # 3328 sub GetRingsWithSizeLessThan { 3329 my($This, $RingSize) = @_; 3330 3331 return $This->_GetRings($This->GetCyclesWithSizeLessThan($RingSize)); 3332 } 3333 3334 # Get rings with size greater than a specific size as an array containing references to arrays with ring atoms... 3335 # 3336 sub GetRingsWithSizeGreaterThan { 3337 my($This, $RingSize) = @_; 3338 3339 return $This->_GetRings($This->GetCyclesWithSizeGreaterThan($RingSize)); 3340 } 3341 3342 # Generate an array of bond objects for an array of ring atoms and return an array 3343 # of bond objects... 3344 # 3345 sub GetRingBonds { 3346 my($This, @RingAtoms) = @_; 3347 my(@Bonds); 3348 3349 @Bonds = (); 3350 if (!@RingAtoms) { 3351 # Return an empty ring bonds list... 3352 return @Bonds; 3353 } 3354 3355 my(@RingAtomIDs); 3356 3357 @RingAtomIDs = (); 3358 @RingAtomIDs = $This->_GetAtomsIDsFromAtoms(@RingAtoms); 3359 if (!@RingAtomIDs) { 3360 carp "Warning: ${ClassName}->GetRingBonds: No ring bonds retrieved: Atom IDs couldn't be retrieved for specified atoms..."; 3361 return @Bonds; 3362 } 3363 3364 # Add start atom to the end to make it a cyclic path for ring: It's taken out during conversion 3365 # of cyclic path to a ring... 3366 push @RingAtomIDs, $RingAtomIDs[0]; 3367 3368 return $This->_GetPathBonds(@RingAtomIDs); 3369 } 3370 3371 # Generate an array containing references to arrays of ring bond objects for rings specified 3372 # in an array of references to ring atoms... 3373 # 3374 sub GetRingBondsFromRings { 3375 my($This, @RingAtomsSets) = @_; 3376 my($RingAtomsRef, @RingBondsSets); 3377 3378 @RingBondsSets = (); 3379 for $RingAtomsRef (@RingAtomsSets) { 3380 my(@RingBonds); 3381 @RingBonds = $This->GetRingBonds(@{$RingAtomsRef}); 3382 3383 push @RingBondsSets, \@RingBonds; 3384 } 3385 3386 return @RingBondsSets; 3387 } 3388 3389 # Does molecule has any fused rings? 3390 # 3391 sub HasFusedRings { 3392 my($This) = @_; 3393 3394 return $This->HasFusedCycles(); 3395 } 3396 3397 # Get references to array of fused ring sets and non-fused rings. Fused ring sets array reference 3398 # contains refernces to arrays of rings; Non-fused rings array reference contains references to 3399 # arrays of ring atoms... 3400 # rings. 3401 # 3402 sub GetFusedAndNonFusedRings { 3403 my($This) = @_; 3404 my($FusedCyclesSetsRef, $NonFusedCyclesRef, @FusedRingSets, @NonFusedRings); 3405 3406 @FusedRingSets = (); @NonFusedRings = (); 3407 ($FusedCyclesSetsRef, $NonFusedCyclesRef) = $This->GetFusedAndNonFusedCycles(); 3408 if (!(defined($FusedCyclesSetsRef) && defined($NonFusedCyclesRef))) { 3409 return (\@FusedRingSets, \@NonFusedRings); 3410 } 3411 my($FusedCyclesSetRef); 3412 3413 for $FusedCyclesSetRef (@{$FusedCyclesSetsRef}) { 3414 my(@FusedRingSet); 3415 @FusedRingSet = (); 3416 @FusedRingSet = $This->_GetRings(@{$FusedCyclesSetRef}); 3417 push @FusedRingSets, \@FusedRingSet; 3418 } 3419 3420 @NonFusedRings = $This->_GetRings(@{$NonFusedCyclesRef}); 3421 3422 return (\@FusedRingSets, \@NonFusedRings); 3423 } 3424 3425 # Get rings as an array containing references to arrays with ring atoms... 3426 # 3427 sub _GetRings { 3428 my($This, @CyclicPaths) = @_; 3429 my($CyclicPath, @Rings); 3430 3431 @Rings = (); 3432 if (!@CyclicPaths) { 3433 return @Rings; 3434 } 3435 if (!@CyclicPaths) { 3436 # Return an empty ring list... 3437 return @Rings; 3438 } 3439 3440 for $CyclicPath (@CyclicPaths) { 3441 my(@RingAtoms); 3442 @RingAtoms = (); 3443 push @RingAtoms, $This->_GetRing($CyclicPath); 3444 3445 push @Rings, \@RingAtoms; 3446 } 3447 return @Rings; 3448 } 3449 3450 # Get aromatic rings as an array containing references to arrays with ring atoms... 3451 # 3452 sub _GetAromaticRings { 3453 my($This, @CyclicPaths) = @_; 3454 my($RingAtomsRef, @Rings, @AromaticRings); 3455 3456 @AromaticRings = (); 3457 @Rings = $This->_GetRings(@CyclicPaths); 3458 3459 if (!@Rings) { 3460 return @AromaticRings; 3461 } 3462 RING: for $RingAtomsRef (@Rings) { 3463 if (!$This->IsRingAromatic(@{$RingAtomsRef})) { 3464 next RING; 3465 } 3466 my(@RingAtoms); 3467 @RingAtoms = (); 3468 push @RingAtoms, @{$RingAtomsRef}; 3469 3470 push @AromaticRings, \@RingAtoms; 3471 } 3472 return @AromaticRings; 3473 } 3474 3475 # Map atom IDs in cyclic path to atoms and return a reference to an array containing ring atoms... 3476 # 3477 # Note: 3478 # . Start and end vertex is same for cyclic paths. So end atom is removed before 3479 # returning atoms array as ring atoms... 3480 # 3481 sub _GetRing { 3482 my($This, $CyclicPath) = @_; 3483 my(@RingAtoms); 3484 3485 @RingAtoms = (); 3486 if (!defined $CyclicPath) { 3487 # Return an empty atoms list... 3488 return @RingAtoms; 3489 } 3490 3491 @RingAtoms = $This->_GetPathAtoms($CyclicPath); 3492 if (@RingAtoms) { 3493 pop @RingAtoms; 3494 } 3495 return @RingAtoms; 3496 } 3497 3498 # Map atom IDs to atoms and return a reference to an array containing these atoms... 3499 # 3500 sub _GetPathAtoms { 3501 my($This, $Path) = @_; 3502 my(@PathAtoms); 3503 3504 @PathAtoms = (); 3505 if (!defined $Path) { 3506 carp "Warning: ${ClassName}->_GetPathAtoms: No path atoms retrieved: Path must be defined..."; 3507 return @PathAtoms; 3508 } 3509 my(@AtomIDs); 3510 3511 @AtomIDs = (); 3512 @AtomIDs = $Path->GetVertices(); 3513 3514 @PathAtoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); 3515 3516 return @PathAtoms; 3517 } 3518 3519 # Get bonds for a path specified by atom IDs... 3520 # 3521 sub _GetPathBonds { 3522 my($This, @AtomIDs) = @_; 3523 my($Index, $AtomID1, $AtomID2, @Bonds, @EdgesAtomIDs); 3524 3525 @Bonds = (); @EdgesAtomIDs = (); 3526 3527 if (!@AtomIDs || @AtomIDs == 1) { 3528 return @Bonds; 3529 } 3530 3531 # Setup edges... 3532 for $Index (0 .. ($#AtomIDs - 1) ) { 3533 $AtomID1 = $AtomIDs[$Index]; 3534 $AtomID2 = $AtomIDs[$Index + 1]; 3535 push @EdgesAtomIDs, ($AtomID1, $AtomID2); 3536 } 3537 @Bonds = $This->GetEdgesProperty('Bond', @EdgesAtomIDs); 3538 3539 return @Bonds; 3540 } 3541 3542 # Map atom ID to an atom... 3543 # 3544 sub _GetAtomFromAtomID { 3545 my($This, $AtomID) = @_; 3546 3547 return $This->GetVertexProperty('Atom', $AtomID); 3548 } 3549 3550 # Map atom IDs to atoms and return an array containing these atoms... 3551 # 3552 sub _GetAtomsFromAtomIDs { 3553 my($This, @AtomIDs) = @_; 3554 3555 return $This->GetVerticesProperty('Atom', @AtomIDs); 3556 } 3557 3558 # Map atoms to atom IDs and return an array containing these atoms... 3559 # 3560 sub _GetAtomsIDsFromAtoms { 3561 my($This, @Atoms) = @_; 3562 3563 return map { $_->GetID() } @Atoms; 3564 } 3565 3566 # Get bonded atom pair atom IDs for specified list of atom IDs... 3567 # 3568 sub _GetBondedAtomPairAtomIDsFromAtomIDs { 3569 my($This, @AtomIDs) = @_; 3570 my($AtomIndex1, $AtomID1, $Atom1, $AtomIndex2, $AtomID2, $Atom2, @Atoms, @BondedAtomPairIDs); 3571 3572 @BondedAtomPairIDs = (); 3573 @Atoms = $This->_GetAtomsFromAtomIDs(@AtomIDs); 3574 3575 for $AtomIndex1 ( 0 .. $#Atoms) { 3576 $Atom1 = $Atoms[$AtomIndex1]; 3577 $AtomID1 = $Atom1->GetID(); 3578 3579 ATOMINDEX2: for $AtomIndex2 ( ($AtomIndex1 + 1) .. $#Atoms) { 3580 $Atom2 = $Atoms[$AtomIndex2]; 3581 if (!$Atom1->IsBondedToAtom($Atom2)) { 3582 next ATOMINDEX2; 3583 } 3584 $AtomID2 = $Atom2->GetID(); 3585 3586 push @BondedAtomPairIDs, ($AtomID1, $AtomID2); 3587 } 3588 } 3589 3590 return @BondedAtomPairIDs; 3591 } 3592 3593 # Get bonded atom pair atoms for specified list of atoms... 3594 # 3595 sub _GetBondedAtomPairAtomsFromAtoms { 3596 my($This, @Atoms) = @_; 3597 my($AtomIndex1, $Atom1, $AtomIndex2, $Atom2, @BondedAtomPairAtoms); 3598 3599 @BondedAtomPairAtoms = (); 3600 3601 for $AtomIndex1 ( 0 .. $#Atoms) { 3602 $Atom1 = $Atoms[$AtomIndex1]; 3603 3604 ATOMINDEX2: for $AtomIndex2 ( ($AtomIndex1 + 1) .. $#Atoms) { 3605 $Atom2 = $Atoms[$AtomIndex2]; 3606 if ($Atom1->IsBondedToAtom($Atom2)) { 3607 next ATOMINDEX2; 3608 } 3609 3610 push @BondedAtomPairAtoms, ($Atom1, $Atom2); 3611 } 3612 } 3613 3614 return @BondedAtomPairAtoms; 3615 } 3616 3617 # Is atom in a ring? 3618 # 3619 sub _IsAtomInRing { 3620 my($This, $Atom) = @_; 3621 3622 return $This->IsCyclicVertex($Atom->GetID()); 3623 } 3624 3625 # Is atom not in a ring? 3626 # 3627 sub _IsAtomNotInRing { 3628 my($This, $Atom) = @_; 3629 3630 return $This->IsAcyclicVertex($Atom->GetID()); 3631 } 3632 3633 # Is atom only in one ring? 3634 # 3635 sub _IsAtomInOnlyOneRing { 3636 my($This, $Atom) = @_; 3637 3638 return $This->IsUnicyclicVertex($Atom->GetID()); 3639 } 3640 3641 # Is atom in a ring of specified size? 3642 # 3643 sub _IsAtomInRingOfSize { 3644 my($This, $Atom, $RingSize) = @_; 3645 3646 return $This->GetNumOfVertexCyclesWithSize($Atom->GetID(), $RingSize) ? 1 : 0; 3647 } 3648 3649 # Get size of smallest ring containing specified atom... 3650 # 3651 sub _GetSizeOfSmallestAtomRing { 3652 my($This, $Atom) = @_; 3653 3654 return $This->GetSizeOfSmallestVertexCycle($Atom->GetID()); 3655 } 3656 3657 # Get size of largest ring containing specified atom... 3658 # 3659 sub _GetSizeOfLargestAtomRing { 3660 my($This, $Atom) = @_; 3661 3662 return $This->GetSizeOfLargestVertexCycle($Atom->GetID()); 3663 } 3664 3665 # Get number of rings containing specified atom... 3666 # 3667 sub _GetNumOfAtomRings { 3668 my($This, $Atom) = @_; 3669 3670 return $This->GetNumOfVertexCycles($Atom->GetID()); 3671 } 3672 3673 # Get number of rings with odd size containing specified atom... 3674 # 3675 sub _GetNumOfAtomRingsWithOddSize { 3676 my($This, $Atom) = @_; 3677 3678 return $This->GetNumOfVertexCyclesWithOddSize($Atom->GetID()); 3679 } 3680 3681 # Get number of rings with even size containing specified atom... 3682 # 3683 sub _GetNumOfAtomRingsWithEvenSize { 3684 my($This, $Atom) = @_; 3685 3686 return $This->GetNumOfVertexCyclesWithEvenSize($Atom->GetID()); 3687 } 3688 3689 # Get number of rings with specified size containing specified atom... 3690 # 3691 sub _GetNumOfAtomRingsWithSize { 3692 my($This, $Atom, $RingSize) = @_; 3693 3694 return $This->GetNumOfVertexCyclesWithSize($Atom->GetID(), $RingSize); 3695 } 3696 3697 # Get number of rings with size less than specified containing specified atom... 3698 # 3699 sub _GetNumOfAtomRingsWithSizeLessThan { 3700 my($This, $Atom, $RingSize) = @_; 3701 3702 return $This->GetNumOfVertexCyclesWithSizeLessThan($Atom->GetID(), $RingSize); 3703 } 3704 3705 # Get number of rings with size greater than specified containing specified atom... 3706 # 3707 sub _GetNumOfAtomRingsWithSizeGreaterThan { 3708 my($This, $Atom, $RingSize) = @_; 3709 3710 return $This->GetNumOfVertexCyclesWithSizeGreaterThan($Atom->GetID(), $RingSize); 3711 } 3712 3713 # Get smallest ring as an array containing ring atoms... 3714 # 3715 sub _GetSmallestAtomRing { 3716 my($This, $Atom) = @_; 3717 3718 return $This->_GetRing($This->GetSmallestVertexCycle($Atom->GetID())); 3719 } 3720 3721 # Get odd size rings an array of references to arrays containing ring atoms... 3722 # 3723 sub _GetLargestAtomRing { 3724 my($This, $Atom) = @_; 3725 3726 return $This->_GetRing($This->GetLargestVertexCycle($Atom->GetID())); 3727 } 3728 3729 # Get all rings an array of references to arrays containing ring atoms... 3730 # 3731 sub _GetAtomRings { 3732 my($This, $Atom) = @_; 3733 3734 return $This->_GetRings($This->GetVertexCycles($Atom->GetID())); 3735 } 3736 3737 # Get odd size rings an array of references to arrays containing ring atoms... 3738 # 3739 sub _GetAtomRingsWithOddSize { 3740 my($This, $Atom) = @_; 3741 3742 return $This->_GetRings($This->GetVertexCyclesWithOddSize($Atom->GetID())); 3743 } 3744 3745 # Get even size rings an array of references to arrays containing ring atoms... 3746 # 3747 sub _GetAtomRingsWithEvenSize { 3748 my($This, $Atom) = @_; 3749 3750 return $This->_GetRings($This->GetVertexCyclesWithEvenSize($Atom->GetID())); 3751 } 3752 3753 # Get rings with specified size an array of references to arrays containing ring atoms... 3754 # 3755 sub _GetAtomRingsWithSize { 3756 my($This, $Atom, $RingSize) = @_; 3757 3758 return $This->_GetRings($This->GetVertexCyclesWithSize($Atom->GetID(), $RingSize)); 3759 } 3760 3761 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms... 3762 # 3763 sub _GetAtomRingsWithSizeLessThan { 3764 my($This, $Atom, $RingSize) = @_; 3765 3766 return $This->_GetRings($This->GetVertexCyclesWithSizeLessThan($Atom->GetID(), $RingSize)); 3767 } 3768 3769 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms... 3770 # 3771 sub _GetAtomRingsWithSizeGreaterThan { 3772 my($This, $Atom, $RingSize) = @_; 3773 3774 return $This->_GetRings($This->GetVertexCyclesWithSizeGreaterThan($Atom->GetID(), $RingSize)); 3775 } 3776 3777 # Is bond in a ring? 3778 # 3779 sub _IsBondInRing { 3780 my($This, $Bond) = @_; 3781 my($Atom1, $Atom2); 3782 3783 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3784 3785 return $This->IsCyclicEdge($Atom1->GetID(), $Atom2->GetID()); 3786 } 3787 3788 # Is bond not in a ring? 3789 # 3790 sub _IsBondNotInRing { 3791 my($This, $Bond) = @_; 3792 my($Atom1, $Atom2); 3793 3794 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3795 3796 return $This->IsAcyclicEdge($Atom1->GetID(), $Atom2->GetID()); 3797 } 3798 3799 # Is bond only in one ring? 3800 # 3801 sub _IsBondInOnlyOneRing { 3802 my($This, $Bond) = @_; 3803 my($Atom1, $Atom2); 3804 3805 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3806 3807 return $This->IsUnicyclicEdge($Atom1->GetID(), $Atom2->GetID()); 3808 } 3809 3810 # Is bond in a ring of specified size? 3811 # 3812 sub _IsBondInRingOfSize { 3813 my($This, $Bond, $RingSize) = @_; 3814 my($Atom1, $Atom2); 3815 3816 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3817 3818 return $This->GetNumOfEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize) ? 1 : 0; 3819 } 3820 3821 # Get size of smallest ring containing specified bond... 3822 # 3823 sub _GetSizeOfSmallestBondRing { 3824 my($This, $Bond) = @_; 3825 my($Atom1, $Atom2); 3826 3827 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3828 3829 return $This->GetSizeOfSmallestEdgeCycle($Atom1->GetID(), $Atom2->GetID()); 3830 } 3831 3832 # Get size of largest ring containing specified bond... 3833 # 3834 sub _GetSizeOfLargestBondRing { 3835 my($This, $Bond) = @_; 3836 my($Atom1, $Atom2); 3837 3838 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3839 3840 return $This->GetSizeOfLargestEdgeCycle($Atom1->GetID(), $Atom2->GetID()); 3841 } 3842 3843 # Get number of rings containing specified bond... 3844 # 3845 sub _GetNumOfBondRings { 3846 my($This, $Bond) = @_; 3847 my($Atom1, $Atom2); 3848 3849 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3850 3851 return $This->GetNumOfEdgeCycles($Atom1->GetID(), $Atom2->GetID()); 3852 } 3853 3854 # Get number of rings with odd size containing specified bond... 3855 # 3856 sub _GetNumOfBondRingsWithOddSize { 3857 my($This, $Bond) = @_; 3858 my($Atom1, $Atom2); 3859 3860 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3861 3862 return $This->GetNumOfEdgeCyclesWithOddSize($Atom1->GetID(), $Atom2->GetID()); 3863 } 3864 3865 # Get number of rings with even size containing specified bond... 3866 # 3867 sub _GetNumOfBondRingsWithEvenSize { 3868 my($This, $Bond) = @_; 3869 my($Atom1, $Atom2); 3870 3871 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3872 3873 return $This->GetNumOfEdgeCyclesWithEvenSize($Atom1->GetID(), $Atom2->GetID()); 3874 } 3875 3876 # Get number of rings with specified size containing specified bond... 3877 # 3878 sub _GetNumOfBondRingsWithSize { 3879 my($This, $Bond, $RingSize) = @_; 3880 my($Atom1, $Atom2); 3881 3882 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3883 3884 return $This->GetNumOfEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize); 3885 } 3886 3887 # Get number of rings with size less than specified containing specified bond... 3888 # 3889 sub _GetNumOfBondRingsWithSizeLessThan { 3890 my($This, $Bond, $RingSize) = @_; 3891 my($Atom1, $Atom2); 3892 3893 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3894 3895 return $This->GetNumOfEdgeCyclesWithSizeLessThan($Atom1->GetID(), $Atom2->GetID(), $RingSize); 3896 } 3897 3898 # Get number of rings with size greater than specified containing specified bond... 3899 # 3900 sub _GetNumOfBondRingsWithSizeGreaterThan { 3901 my($This, $Bond, $RingSize) = @_; 3902 my($Atom1, $Atom2); 3903 3904 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3905 3906 return $This->GetNumOfEdgeCyclesWithSizeGreaterThan($Atom1->GetID(), $Atom2->GetID(), $RingSize); 3907 } 3908 3909 # Get smallest ring as an array containing ring atoms... 3910 # 3911 sub _GetSmallestBondRing { 3912 my($This, $Bond) = @_; 3913 my($Atom1, $Atom2); 3914 3915 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3916 3917 return $This->_GetRing($This->GetSmallestEdgeCycle($Atom1->GetID(), $Atom2->GetID())); 3918 } 3919 3920 # Get odd size rings an array of references to arrays containing ring atoms... 3921 # 3922 sub _GetLargestBondRing { 3923 my($This, $Bond) = @_; 3924 my($Atom1, $Atom2); 3925 3926 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3927 3928 return $This->_GetRing($This->GetLargestEdgeCycle($Atom1->GetID(), $Atom2->GetID())); 3929 } 3930 3931 # Get all rings an array of references to arrays containing ring atoms... 3932 # 3933 sub _GetBondRings { 3934 my($This, $Bond) = @_; 3935 my($Atom1, $Atom2); 3936 3937 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3938 3939 return $This->_GetRings($This->GetEdgeCycles($Atom1->GetID(), $Atom2->GetID())); 3940 } 3941 3942 # Get odd size rings an array of references to arrays containing ring atoms... 3943 # 3944 sub _GetBondRingsWithOddSize { 3945 my($This, $Bond) = @_; 3946 my($Atom1, $Atom2); 3947 3948 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3949 3950 return $This->_GetRings($This->GetEdgeCyclesWithOddSize($Atom1->GetID(), $Atom2->GetID())); 3951 } 3952 3953 # Get even size rings an array of references to arrays containing ring atoms... 3954 # 3955 sub _GetBondRingsWithEvenSize { 3956 my($This, $Bond) = @_; 3957 my($Atom1, $Atom2); 3958 3959 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3960 3961 return $This->_GetRings($This->GetEdgeCyclesWithEvenSize($Atom1->GetID(), $Atom2->GetID())); 3962 } 3963 3964 # Get rings with specified size an array of references to arrays containing ring atoms... 3965 # 3966 sub _GetBondRingsWithSize { 3967 my($This, $Bond, $RingSize) = @_; 3968 my($Atom1, $Atom2); 3969 3970 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3971 3972 return $This->_GetRings($This->GetEdgeCyclesWithSize($Atom1->GetID(), $Atom2->GetID(), $RingSize)); 3973 } 3974 3975 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms... 3976 # 3977 sub _GetBondRingsWithSizeLessThan { 3978 my($This, $Bond, $RingSize) = @_; 3979 my($Atom1, $Atom2); 3980 3981 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3982 3983 return $This->_GetRings($This->GetEdgeCyclesWithSizeLessThan($Atom1->GetID(), $Atom2->GetID(), $RingSize)); 3984 } 3985 3986 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms... 3987 # 3988 sub _GetBondRingsWithSizeGreaterThan { 3989 my($This, $Bond, $RingSize) = @_; 3990 my($Atom1, $Atom2); 3991 3992 ($Atom1, $Atom2) = $Bond->GetAtoms(); 3993 3994 return $This->_GetRings($This->GetEdgeCyclesWithSizeGreaterThan($Atom1->GetID(), $Atom2->GetID(), $RingSize)); 3995 } 3996 3997 3998 # Get atom paths starting from a specified atom as a reference to an array containing references 3999 # to arrays with path atoms. 4000 # 4001 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with length 4002 # upto a specified length and sharing of bonds in paths traversed. By default, rings are 4003 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4004 # 4005 # Note: 4006 # . For molecule without any rings, this method returns the same set of atom paths 4007 # as GetAtomPathsStartingAtWithLengthUpto method. 4008 # 4009 sub GetAllAtomPathsStartingAtWithLengthUpto { 4010 my($This, $StartAtom, $Length, $AllowCycles) = @_; 4011 4012 return $This->_GetAtomPathsStartingAt('AllAtomPathsWithLengthUpto', $StartAtom, $Length, $AllowCycles); 4013 } 4014 4015 # Get atom paths starting from a specified atom as a reference to an array containing references 4016 # to arrays with path atoms. 4017 # 4018 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with 4019 # specified length and sharing of bonds in paths traversed. By default, rings are 4020 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4021 # 4022 # Note: 4023 # . For molecule without any rings, this method returns the same set of atom paths 4024 # as GetAtomPathsStartingAtWithLengthUpto method. 4025 # 4026 sub GetAllAtomPathsStartingAtWithLength { 4027 my($This, $StartAtom, $Length, $AllowCycles) = @_; 4028 4029 return $This->_GetAtomPathsStartingAt('AllAtomPathsWithLength', $StartAtom, $Length, $AllowCycles); 4030 } 4031 4032 # Get atom paths starting from a specified atom as a reference to an array containing references 4033 # to arrays with path atoms. 4034 # 4035 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with all 4036 # possible lengths and sharing of bonds in paths traversed. By default, rings are 4037 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4038 # 4039 # Note: 4040 # . For molecule without any rings, this method returns the same set of atom paths 4041 # as GetAtomPathsStartingAt method. 4042 # 4043 sub GetAllAtomPathsStartingAt { 4044 my($This, $StartAtom, $AllowCycles) = @_; 4045 4046 return $This->_GetAtomPathsStartingAt('AllAtomPathsWithAllLengths', $StartAtom, undef, $AllowCycles); 4047 } 4048 4049 # Get atom paths starting from a specified atom as a reference to an array containing references 4050 # to arrays with path atoms. 4051 # 4052 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with length 4053 # upto a specified length and no sharing of bonds in paths traversed. By default, rings are 4054 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4055 # 4056 sub GetAtomPathsStartingAtWithLengthUpto { 4057 my($This, $StartAtom, $Length, $AllowCycles) = @_; 4058 4059 return $This->_GetAtomPathsStartingAt('AtomPathsWithLengthUpto', $StartAtom, $Length, $AllowCycles); 4060 } 4061 4062 # Get atom paths starting from a specified atom as a reference to an array containing references 4063 # to arrays with path atoms. 4064 # 4065 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with 4066 # specified length and no sharing of bonds in paths traversed. By default, rings are 4067 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4068 # 4069 sub GetAtomPathsStartingAtWithLength { 4070 my($This, $StartAtom, $Length, $AllowCycles) = @_; 4071 4072 return $This->_GetAtomPathsStartingAt('AtomPathsWithLength', $StartAtom, $Length, $AllowCycles); 4073 } 4074 4075 # Get atom paths starting from a specified atom as a reference to an array containing references 4076 # to arrays with path atoms. 4077 # 4078 # Path atoms atoms correspond to to all possible paths for specified atom in molecule with all 4079 # possible lengths and no sharing of bonds in paths traversed. By default, rings are 4080 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4081 # 4082 # 4083 sub GetAtomPathsStartingAt { 4084 my($This, $StartAtom, $AllowCycles) = @_; 4085 4086 return $This->_GetAtomPathsStartingAt('AtomPathsWithAllLengths', $StartAtom, undef, $AllowCycles); 4087 } 4088 4089 # Get atom paths as an array containing references to arrays with path atoms... 4090 # 4091 sub _GetAtomPathsStartingAt { 4092 my($This, $Mode, $StartAtom, $Length, $AllowCycles) = @_; 4093 my(@AtomPaths); 4094 4095 @AtomPaths = (); 4096 if (!defined $StartAtom) { 4097 carp "Warning: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Start atom is not defined..."; 4098 return @AtomPaths; 4099 } 4100 if (!$This->HasAtom($StartAtom)) { 4101 carp "Warning: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Start atom doesn't exist..."; 4102 return @AtomPaths; 4103 } 4104 my($StartAtomID, @Paths); 4105 4106 $StartAtomID = $StartAtom->GetID(); 4107 @Paths = (); 4108 4109 # Collect appropriate atom paths... 4110 MODE: { 4111 if ($Mode =~ /^AtomPathsWithLengthUpto$/i) { @Paths = $This->GetPathsStartingAtWithLengthUpto($StartAtomID, $Length, $AllowCycles); last MODE; } 4112 if ($Mode =~ /^AtomPathsWithLength$/i) { @Paths = $This->GetPathsStartingAtWithLength($StartAtomID, $Length, $AllowCycles); last MODE; } 4113 if ($Mode =~ /^AtomPathsWithAllLengths$/i) { @Paths = $This->GetPathsStartingAt($StartAtomID, $AllowCycles); last MODE; } 4114 4115 if ($Mode =~ /^AllAtomPathsWithLengthUpto$/i) { @Paths = $This->GetAllPathsStartingAtWithLengthUpto($StartAtomID, $Length, $AllowCycles); last MODE; } 4116 if ($Mode =~ /^AllAtomPathsWithLength$/i) { @Paths = $This->GetAllPathsStartingAtWithLength($StartAtomID, $Length, $AllowCycles); last MODE; } 4117 if ($Mode =~ /^AllAtomPathsWithAllLengths$/i) { @Paths = $This->GetAllPathsStartingAt($StartAtomID, $AllowCycles); last MODE; } 4118 4119 print "Warn: ${ClassName}->_GetAtomPathsStartingAt: No atom paths retrieved: Mode, $Mode, is not supported..."; 4120 return @AtomPaths; 4121 } 4122 return $This->_GetAtomPathsFromPaths(\@Paths); 4123 } 4124 4125 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4126 # path atoms. 4127 # 4128 # Path atoms correspond to to all possible paths for each atom in molecule with length 4129 # upto a specified length and sharing of bonds in paths traversed. By default, rings are 4130 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4131 # 4132 # Notes: 4133 # . For molecule without any rings, this method returns the same set of atom paths 4134 # as GetAtomPathsWithLengthUpto method. 4135 # 4136 sub GetAllAtomPathsWithLengthUpto { 4137 my($This, $Length, $AllowCycles) = @_; 4138 4139 return $This->_GetAtomPaths('AllAtomPathsWithLengthUpto', $Length, $AllowCycles); 4140 } 4141 4142 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4143 # path atoms. 4144 # 4145 # Path atoms correspond to to all possible paths for each atom in molecule with 4146 # a specified length and sharing of bonds in paths traversed. By default, rings are 4147 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4148 # 4149 # Notes: 4150 # . For molecule without any rings, this method returns the same set of atom paths 4151 # as GetAtomPathsWithLengthUpto method. 4152 # 4153 sub GetAllAtomPathsWithLength { 4154 my($This, $Length, $AllowCycles) = @_; 4155 4156 return $This->_GetAtomPaths('AllAtomPathsWithLength', $Length, $AllowCycles); 4157 } 4158 4159 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4160 # path atoms. 4161 # 4162 # Path atoms correspond to to all possible paths for each atom in molecule with all 4163 # possible lengths and sharing of bonds in paths traversed. By default, rings are 4164 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4165 # 4166 # Notes: 4167 # . For molecule without any rings, this method returns the same set of atom paths 4168 # as GetAtomPaths method. 4169 # 4170 sub GetAllAtomPaths { 4171 my($This, $AllowCycles) = @_; 4172 4173 return $This->_GetAtomPaths('AllAtomPathsWithAllLengths', undef, $AllowCycles); 4174 } 4175 4176 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4177 # path atoms. 4178 # 4179 # Path atoms correspond to to all possible paths for each atom in molecule with length 4180 # upto a specified length and no sharing of bonds in paths traversed. By default, rings are 4181 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4182 # 4183 sub GetAtomPathsWithLengthUpto { 4184 my($This, $Length, $AllowCycles) = @_; 4185 4186 return $This->_GetAtomPaths('AtomPathsWithLengthUpto', $Length, $AllowCycles); 4187 } 4188 4189 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4190 # path atoms. 4191 # 4192 # Path atoms correspond to to all possible paths for each atom in molecule with 4193 # a specified length and no sharing of bonds in paths traversed. By default, rings are 4194 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4195 # 4196 sub GetAtomPathsWithLength { 4197 my($This, $Length, $AllowCycles) = @_; 4198 4199 return $This->_GetAtomPaths('AtomPathsWithLength', $Length, $AllowCycles); 4200 } 4201 4202 4203 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4204 # path atoms. 4205 # 4206 # Path atoms correspond to to all possible paths for each atom in molecule with all 4207 # possible lengths and no sharing of bonds in paths traversed. By default, rings are 4208 # included in paths. A path containing a ring is terminated at an atom completing the ring. 4209 # 4210 sub GetAtomPaths { 4211 my($This, $AllowCycles) = @_; 4212 4213 return $This->_GetAtomPaths('AtomPathsWithAllLengths', undef, $AllowCycles); 4214 } 4215 4216 # Get atom paths for all atoms as a reference to an array containing references to arrays with 4217 # path atoms. 4218 # 4219 sub _GetAtomPaths { 4220 my($This, $Mode, $Length, $AllowCycles) = @_; 4221 my($PathsRef, @AtomPaths); 4222 4223 @AtomPaths = (); 4224 # Collect appropriate atom paths... 4225 MODE: { 4226 if ($Mode =~ /^AtomPathsWithLengthUpto$/i) { $PathsRef = $This->GetPathsWithLengthUpto($Length, $AllowCycles); last MODE; } 4227 if ($Mode =~ /^AtomPathsWithLength$/i) { $PathsRef = $This->GetPathsWithLength($Length, $AllowCycles); last MODE; } 4228 if ($Mode =~ /^AtomPathsWithAllLengths$/i) { $PathsRef = $This->GetPaths($AllowCycles); last MODE; } 4229 4230 if ($Mode =~ /^AllAtomPathsWithLengthUpto$/i) { $PathsRef = $This->GetAllPathsWithLengthUpto($Length, $AllowCycles); last MODE; } 4231 if ($Mode =~ /^AllAtomPathsWithLength$/i) { $PathsRef = $This->GetAllPathsWithLength($Length, $AllowCycles); last MODE; } 4232 if ($Mode =~ /^AllAtomPathsWithAllLengths$/i) { $PathsRef = $This->GetAllPaths($AllowCycles); last MODE; } 4233 4234 print "Warn: ${ClassName}->_GetAtomPaths: No atom paths retrieved: Mode, $Mode, is not supported..."; 4235 return \@AtomPaths; 4236 } 4237 return $This->_GetAtomPathsFromPaths($PathsRef); 4238 } 4239 4240 # Get atom paths as an array reference containing references to arrays with path atoms... 4241 # 4242 sub _GetAtomPathsFromPaths { 4243 my($This, $PathsRef) = @_; 4244 my($Path, @AtomPaths); 4245 4246 @AtomPaths = (); 4247 if (!defined $PathsRef) { 4248 return \@AtomPaths; 4249 } 4250 if (!@{$PathsRef}) { 4251 # Return an empty atom paths list... 4252 return \@AtomPaths; 4253 } 4254 for $Path (@{$PathsRef}) { 4255 my(@PathAtoms); 4256 @PathAtoms = (); 4257 @PathAtoms = $This->_GetAtomPathFromPath($Path); 4258 4259 push @AtomPaths, \@PathAtoms; 4260 } 4261 return \@AtomPaths; 4262 } 4263 4264 # Generate an array of bond objects for an array of path atoms and return an array 4265 # of bond objects... 4266 # 4267 sub GetAtomPathBonds { 4268 my($This, @PathAtoms) = @_; 4269 my(@Bonds); 4270 4271 if (!@PathAtoms) { 4272 # Return an empty ring bonds list... 4273 return @Bonds; 4274 } 4275 my(@PathAtomIDs); 4276 4277 @PathAtomIDs = (); 4278 @PathAtomIDs = $This->_GetAtomsIDsFromAtoms(@PathAtoms); 4279 4280 return $This->_GetPathBonds(@PathAtomIDs); 4281 } 4282 4283 # Map atom IDs in path to atoms and return a reference to an array containing ring atoms... 4284 # 4285 sub _GetAtomPathFromPath { 4286 my($This, $Path) = @_; 4287 my(@PathAtoms); 4288 4289 @PathAtoms = (); 4290 if (!defined $Path) { 4291 # Return an empty atoms list... 4292 return @PathAtoms; 4293 } 4294 4295 return $This->_GetPathAtoms($Path); 4296 } 4297 4298 # Get atom paths between two specified atoms as a reference to an array containing references 4299 # to arrays with path atoms. For molecules with rings, atom paths array contains may contain 4300 # two paths. 4301 # 4302 sub GetAtomPathsBetween { 4303 my($This, $StartAtom, $EndAtom) = @_; 4304 my(@AtomPaths); 4305 4306 @AtomPaths = (); 4307 if (!(defined($StartAtom) && $This->HasAtom($StartAtom))) { 4308 carp "Warning: ${ClassName}->_GetAtomPathsBetween: No atom paths retrieved: Start atom is not defined or it doesn't exist..."; 4309 return @AtomPaths; 4310 } 4311 if (!(defined($EndAtom) && $This->HasAtom($EndAtom))) { 4312 carp "Warning: ${ClassName}->_GetAtomPathsBetween: No atom paths retrieved: End atom is not defined or it doesn't exist..."; 4313 return @AtomPaths; 4314 } 4315 return $This->_GetAtomPathsBetween($StartAtom, $EndAtom); 4316 } 4317 4318 # Get atom paths between two specified atoms as a reference to an array containing references 4319 # to arrays with path atoms. 4320 # 4321 sub _GetAtomPathsBetween { 4322 my($This, $StartAtom, $EndAtom) = @_; 4323 my($StartAtomID, $EndAtomID, @Paths); 4324 4325 $StartAtomID = $StartAtom->GetID(); 4326 $EndAtomID = $EndAtom->GetID(); 4327 4328 @Paths = (); 4329 @Paths = $This->GetPathsBetween($StartAtomID, $EndAtomID); 4330 4331 return $This->_GetAtomPathsFromPaths(\@Paths); 4332 } 4333 4334 # Get atom neighborhoods around a specified atom as an array containing references 4335 # to arrays with neighborhood atoms at different radii upto specified radius... 4336 # 4337 sub GetAtomNeighborhoodsWithRadiusUpto { 4338 my($This, $StartAtom, $Radius) = @_; 4339 4340 return $This->_GetAtomNeighborhoods('RadiusUpto', $StartAtom, $Radius); 4341 } 4342 4343 # Get atom neighborhoods around a specified atom as an array containing references 4344 # to arrays with neighborhood atoms at possible radii... 4345 # 4346 sub GetAtomNeighborhoods { 4347 my($This, $StartAtom) = @_; 4348 4349 return $This->_GetAtomNeighborhoods('AllRadii', $StartAtom, undef); 4350 } 4351 4352 # Get atom neighborhood around a specified atom, along with their successor connected atoms, collected 4353 # with in a specified radius as a list containing references to lists with first value corresponding to neighborhood 4354 # atom at a specific radius and second value as reference to a list containing its successor connected atoms. 4355 # 4356 # For a neighborhood atom at each radius level, the successor connected atoms correspond to the 4357 # neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last 4358 # radius level don't contain any successor atoms which fall outside the range of specified radius. 4359 # 4360 sub GetAtomNeighborhoodsWithSuccessorAtomsAndRadiusUpto { 4361 my($This, $StartAtom, $Radius) = @_; 4362 4363 return $This->_GetAtomNeighborhoods('WithSuccessorsAndRadiusUpto', $StartAtom, $Radius); 4364 } 4365 4366 # Get atom neighborhood around a specified atom, along with their successor connected atoms, collected 4367 # at all radii as a list containing references to lists with first value corresponding to neighborhood 4368 # atom at a specific radius and second value as reference to a list containing its successor connected atoms. 4369 # 4370 # For a neighborhood atom at each radius level, the successor connected atoms correspond to the 4371 # neighborhood atoms at the next radius level. Consequently, the neighborhood atoms at the last 4372 # radius level don't contain any successor atoms which fall outside the range of specified radius. 4373 # 4374 # 4375 sub GetAtomNeighborhoodsWithSuccessorAtoms { 4376 my($This, $StartAtom) = @_; 4377 4378 return $This->_GetAtomNeighborhoods('WithSuccessorsAndAllRadii', $StartAtom, undef); 4379 } 4380 4381 # Get atom neighborhoods... 4382 # 4383 sub _GetAtomNeighborhoods { 4384 my($This, $Mode, $StartAtom, $Radius) = @_; 4385 my(@AtomNeighborhoods); 4386 4387 @AtomNeighborhoods = (); 4388 4389 if (!(defined($StartAtom) && $This->HasAtom($StartAtom))) { 4390 carp "Warning: ${ClassName}->_GetAtomNeighborhoods: No atom neighborhoods retrieved: Start atom is not defined or it doesn't exist..."; 4391 return @AtomNeighborhoods; 4392 } 4393 if ($Mode =~ /^(RadiusUpto|WithSuccessorsAndRadiusUpto)$/i) { 4394 if (!(defined($Radius) && $Radius > 0)) { 4395 carp "Warning: ${ClassName}->_GetAtomNeighborhoods: No atom neighborhoods retrieved: Radius is not defined or it's <= 0 ..."; 4396 return @AtomNeighborhoods; 4397 } 4398 } 4399 4400 # Collect neighborhood atom IDs... 4401 my($StartAtomID, @NeighborhoodAtomIDs, @NeighborhoodAtomIDsWithSuccessors); 4402 4403 @NeighborhoodAtomIDs = (); @NeighborhoodAtomIDsWithSuccessors = (); 4404 $StartAtomID = $StartAtom->GetID(); 4405 4406 MODE: { 4407 if ($Mode =~ /^RadiusUpto$/i) { @NeighborhoodAtomIDs = $This->GetNeighborhoodVerticesWithRadiusUpto($StartAtomID, $Radius); last MODE; } 4408 if ($Mode =~ /^AllRadii$/i) { @NeighborhoodAtomIDs = $This->GetNeighborhoodVertices($StartAtomID); last MODE; } 4409 4410 if ($Mode =~ /^WithSuccessorsAndRadiusUpto$/i) { @NeighborhoodAtomIDsWithSuccessors = $This->GetNeighborhoodVerticesWithSuccessorsAndRadiusUpto($StartAtomID, $Radius); last MODE; } 4411 if ($Mode =~ /^WithSuccessorsAndAllRadii$/i) { @NeighborhoodAtomIDsWithSuccessors = $This->GetNeighborhoodVerticesWithSuccessors($StartAtomID); last MODE; } 4412 4413 print "Warn: ${ClassName}->_GetAtomNeighborhood: No atom neighborhoods retrieved: Mode, $Mode, is not supported..."; 4414 return @AtomNeighborhoods; 4415 } 4416 if ($Mode =~ /^(RadiusUpto|AllRadii)$/i) { 4417 return $This->_GetNeighborhoodAtomsFromAtomIDs(\@NeighborhoodAtomIDs); 4418 } 4419 elsif ($Mode =~ /^(WithSuccessorsAndRadiusUpto|WithSuccessorsAndAllRadii)$/i) { 4420 return $This->_GetNeighborhoodAtomsWithSuccessorsFromAtomIDs(\@NeighborhoodAtomIDsWithSuccessors); 4421 } 4422 4423 return @AtomNeighborhoods; 4424 } 4425 4426 # Map neighborhood atom IDs to atoms... 4427 # 4428 sub _GetNeighborhoodAtomsFromAtomIDs { 4429 my($This, $NeighborhoodsAtomIDsRef) = @_; 4430 my($NeighborhoodAtomIDsRef, @AtomNeighborhoods); 4431 4432 @AtomNeighborhoods = (); 4433 for $NeighborhoodAtomIDsRef (@{$NeighborhoodsAtomIDsRef}) { 4434 my(@AtomNeighborhood); 4435 4436 @AtomNeighborhood = (); 4437 @AtomNeighborhood = $This->_GetAtomsFromAtomIDs(@{$NeighborhoodAtomIDsRef}); 4438 push @AtomNeighborhoods, \@AtomNeighborhood; 4439 } 4440 return @AtomNeighborhoods; 4441 } 4442 4443 # Map neighborhood atom IDs with successors to atoms... 4444 # 4445 sub _GetNeighborhoodAtomsWithSuccessorsFromAtomIDs { 4446 my($This, $NeighborhoodsAtomIDsWithSuccessorsRef) = @_; 4447 my($Depth, $NeighborhoodAtomIDsWithSuccessorsRef, $NeighborhoodAtomIDWithSuccessorsRef, $NeighborhoodAtomID, $NeighborhoodAtomSuccessorsIDsRef, @AtomNeighborhoods); 4448 4449 $Depth = 0; 4450 @AtomNeighborhoods = (); 4451 4452 # Go over neighborhoods at each level... 4453 for $NeighborhoodAtomIDsWithSuccessorsRef (@{$NeighborhoodsAtomIDsWithSuccessorsRef}) { 4454 @{$AtomNeighborhoods[$Depth]} = (); 4455 4456 # Go over the neighborhood atoms and their successors at a specific level.. 4457 for $NeighborhoodAtomIDWithSuccessorsRef (@{$NeighborhoodAtomIDsWithSuccessorsRef}) { 4458 my($NeighborhoodAtom, @NeighborhoodAtomWithSuccessors, @NeighborhoodAtomSuccessorAtoms); 4459 4460 @NeighborhoodAtomWithSuccessors = (); @NeighborhoodAtomSuccessorAtoms = (); 4461 ($NeighborhoodAtomID, $NeighborhoodAtomSuccessorsIDsRef) = @{$NeighborhoodAtomIDWithSuccessorsRef}; 4462 4463 # Map atom IDs to atoms... 4464 $NeighborhoodAtom = $This->_GetAtomFromAtomID($NeighborhoodAtomID); 4465 if (@{$NeighborhoodAtomSuccessorsIDsRef}) { 4466 @NeighborhoodAtomSuccessorAtoms = $This->_GetAtomsFromAtomIDs(@{$NeighborhoodAtomSuccessorsIDsRef}); 4467 } 4468 4469 # Store an atom and its successors at each level in an array... 4470 push @NeighborhoodAtomWithSuccessors, ($NeighborhoodAtom, \@NeighborhoodAtomSuccessorAtoms); 4471 4472 push @{$AtomNeighborhoods[$Depth]} , \@NeighborhoodAtomWithSuccessors; 4473 } 4474 $Depth++; 4475 } 4476 return @AtomNeighborhoods; 4477 } 4478 4479 # Get next object ID... 4480 sub _GetNewObjectID { 4481 $ObjectID++; 4482 return $ObjectID; 4483 } 4484 4485 # Is aromatic property set for the molecule? 4486 sub IsAromatic { 4487 my($This) = @_; 4488 my($Aromatic); 4489 4490 $Aromatic = $This->GetAromatic(); 4491 4492 return (defined($Aromatic) && $Aromatic) ? 1 : 0; 4493 } 4494 4495 # Does molecule contains any atoms with non-zero Z coordiantes? 4496 sub IsThreeDimensional { 4497 my($This) = @_; 4498 my($Atom, @Atoms); 4499 4500 @Atoms = $This->GetAtoms(); 4501 ATOM: for $Atom (@Atoms) { 4502 if ($Atom->GetZ() != 0) { 4503 return 1; 4504 } 4505 } 4506 return 0; 4507 } 4508 4509 # Does molecule contains any atoms with non-zero X or Y coordinates 4510 # and only zero Z-coordinates? 4511 sub IsTwoDimensional { 4512 my($This) = @_; 4513 my($Atom, @Atoms); 4514 4515 @Atoms = $This->GetAtoms(); 4516 ATOM: for $Atom (@Atoms) { 4517 if ($Atom->GetZ() != 0) { 4518 return 0; 4519 } 4520 if ($Atom->GetX() != 0 || $Atom->GetY() != 0) { 4521 return 1; 4522 } 4523 } 4524 return 0; 4525 } 4526 4527 # Get dimensionality of the molecule using one of the following two methods: 4528 # . Using explicitly set Dimensionality 4529 # . Going over atomic coordinates 4530 # 4531 # The valid dimensionality values are: 4532 # . 3D - Three dimensional: One of X, Y or Z coordinate is non-zero 4533 # . 2D - Two dimensional: One of X or Y coordinate is non-zero; All Z coordinates are zero 4534 # . 0D - Zero dimensional: All atomic coordinates are zero 4535 # 4536 sub GetDimensionality { 4537 my($This) = @_; 4538 4539 # Is Dimensionality property explicitly set? 4540 if ($This->HasProperty('Dimensionality')) { 4541 return $This->GetProperty('Dimensionality'); 4542 } 4543 my($Atom, @Atoms); 4544 4545 @Atoms = $This->GetAtoms(); 4546 ATOM: for $Atom (@Atoms) { 4547 if ($Atom->GetZ() != 0) { 4548 return '3D'; 4549 } 4550 if ($Atom->GetX() != 0 || $Atom->GetY() != 0) { 4551 return '2D'; 4552 } 4553 } 4554 return '0D'; 4555 } 4556 4557 # Is it a molecule object? 4558 sub IsMolecule ($) { 4559 my($Object) = @_; 4560 4561 return _IsMolecule($Object); 4562 } 4563 4564 # Return a string containing vertices, edges and other properties... 4565 sub StringifyMolecule { 4566 my($This) = @_; 4567 my($MoleculeString, $ID, $Name, $NumOfAtoms, $NumOfBonds, $MolecularFormula, $NumOfRings, $MolecularWeight, $ExactMass, $FormalCharge, $SpinMultiplicity, $FreeRadicalElectrons, $Charge, $ElementsRef, $ElementsCompositionRef, $ElementalComposition); 4568 4569 $ID = $This->GetID(); 4570 $Name = $This->GetName(); 4571 $NumOfAtoms = $This->GetNumOfAtoms(); 4572 $NumOfBonds = $This->GetNumOfBonds(); 4573 4574 $NumOfRings = $This->GetNumOfRings(); 4575 if (!defined $NumOfRings) { 4576 $NumOfRings = 'undefined'; 4577 } 4578 4579 $MolecularFormula = $This->GetMolecularFormula(); 4580 4581 $MolecularWeight = $This->GetMolecularWeight(); 4582 $MolecularWeight = round($MolecularWeight, 4) + 0; 4583 4584 $ExactMass = $This->GetExactMass(); 4585 $ExactMass = round($ExactMass, 4) + 0; 4586 4587 $FormalCharge = $This->GetFormalCharge(); 4588 $Charge = $This->GetCharge(); 4589 4590 $SpinMultiplicity = $This->GetSpinMultiplicity(); 4591 $FreeRadicalElectrons = $This->GetFreeRadicalElectrons(); 4592 4593 ($ElementsRef, $ElementsCompositionRef) = $This->GetElementalComposition(); 4594 $ElementalComposition = 'None'; 4595 if (defined($ElementsRef) && @{$ElementsRef}) { 4596 $ElementalComposition = "[ " . FormatElementalCompositionInformation($ElementsRef, $ElementsCompositionRef) . " ]"; 4597 } 4598 4599 $MoleculeString = "Molecule: ID: $ID; Name: \"$Name\"; NumOfAtoms: $NumOfAtoms; NumOfBonds: $NumOfBonds; NumOfRings: $NumOfRings; MolecularFormula: $MolecularFormula; MolecularWeight: $MolecularWeight; ExactMass: $ExactMass; FormalCharge: $FormalCharge; Charge: $Charge; SpinMultiplicity: $SpinMultiplicity; FreeRadicalElectrons: $FreeRadicalElectrons; ElementalComposition: $ElementalComposition"; 4600 4601 return $MoleculeString; 4602 } 4603 4604 # Load appropriate atom data files from <MayaChemTools>/lib directory used by various 4605 # object methods in the current class... 4606 # 4607 sub _LoadMoleculeClassData { 4608 my($MayaChemToolsLibDir); 4609 4610 $MayaChemToolsLibDir = GetMayaChemToolsLibDirName(); 4611 4612 # Load and process data for aromaticity models... 4613 _LoadAromaticityModelsData($MayaChemToolsLibDir); 4614 _ProcessAromaticityModelsData(); 4615 } 4616 4617 # 4618 # Load data for supported aromaticity models... 4619 # 4620 sub _LoadAromaticityModelsData { 4621 my($MayaChemToolsLibDir) = @_; 4622 my($DataFile, $Index, $InDelim, $Line, $NumOfCols, $ParameterName, $ParameterValue, $ModelName, @ColLabels, @LineWords, %ParameterNames, %ColIndexToModelName, %SupportedParameterNames); 4623 4624 %AromaticityModelsDataMap = (); 4625 %CanonicalAromaticityModelNamesMap = (); 4626 4627 # File format: 4628 # 4629 # "ParameterName","MDLAromaticityModel","TriposAromaticityModel","MMFFAromaticityModel","ChemAxonBasicAromaticityModel","ChemAxonGeneralAromaticityModel","DaylightAromaticityModel","MayaChemToolsAromaticityModel" 4630 # "AllowHeteroRingAtoms","No","No","Yes","Yes","Yes","Yes","Yes" 4631 # 4632 $DataFile = $MayaChemToolsLibDir . "/data/AromaticityModelsData.csv"; 4633 if (! -e "$DataFile") { 4634 croak "Error: ${ClassName}::_LoadAromaticityModelsData: MayaChemTools package file, $DataFile, is missing: Possible installation problems..."; 4635 } 4636 4637 # Setup a list of currently supported aromaticity parameters... 4638 # 4639 my(@KnownNames); 4640 @KnownNames = qw(AllowHeteroRingAtoms HeteroRingAtomsList AllowExocyclicDoubleBonds AllowHomoNuclearExocyclicDoubleBonds AllowElectronegativeRingAtomExocyclicDoubleBonds AllowRingAtomFormalCharge AllowHeteroRingAtomFormalCharge MinimumRingSize); 4641 4642 %SupportedParameterNames = (); 4643 for $ParameterName (@KnownNames) { 4644 $SupportedParameterNames{$ParameterName} = $ParameterName; 4645 } 4646 4647 $InDelim = "\,"; 4648 open DATAFILE, "$DataFile" or croak "Couldn't open $DataFile: $! ..."; 4649 4650 # Skip lines up to column labels... 4651 LINE: while ($Line = GetTextLine(\*DATAFILE)) { 4652 if ($Line !~ /^#/) { 4653 last LINE; 4654 } 4655 } 4656 @ColLabels= quotewords($InDelim, 0, $Line); 4657 $NumOfCols = @ColLabels; 4658 4659 %ColIndexToModelName = (); 4660 4661 # Process names of aromaticity models... 4662 for $Index (1 .. $#ColLabels) { 4663 $ModelName = $ColLabels[$Index]; 4664 $ModelName =~ s/ //g; 4665 4666 if (exists $AromaticityModelsDataMap{$ModelName}) { 4667 croak "Error: ${ClassName}::_LoadAromaticityModelsData: The aromaticity model name, $ModelName, in $DataFile has already exists.\nLine: $Line..."; 4668 } 4669 %{$AromaticityModelsDataMap{$ModelName}} = (); 4670 4671 # Cannonicalize aromatic model name by converting into all lowercase... 4672 $CanonicalAromaticityModelNamesMap{lc($ModelName)} = $ModelName; 4673 4674 $ColIndexToModelName{$Index} = $ModelName; 4675 } 4676 4677 # Process paramater name and their values for specified aromaticity models... 4678 # 4679 %ParameterNames = (); 4680 LINE: while ($Line = GetTextLine(\*DATAFILE)) { 4681 if ($Line =~ /^#/) { 4682 next LINE; 4683 } 4684 @LineWords = (); 4685 @LineWords = quotewords($InDelim, 0, $Line); 4686 if (@LineWords != $NumOfCols) { 4687 croak "Error: ${ClassName}::_LoadAromaticityModelsData: The number of data fields, @LineWords, in $DataFile must be $NumOfCols.\nLine: $Line..."; 4688 } 4689 4690 # Process parameter name and values for aromaticity models... 4691 # 4692 $ParameterName = $LineWords[0]; 4693 4694 if (!exists $SupportedParameterNames{$ParameterName}) { 4695 carp "Warning: ${ClassName}::_LoadAromaticityModelsData: The current release of MayaChemTools doesn't support aromaticity model parameter name, $ParameterName, specified in $DataFile. It would be ignore during aromaticity detection.\nLine: $Line..."; 4696 } 4697 4698 if (exists $ParameterNames{$ParameterName}) { 4699 carp "Warning: ${ClassName}::_LoadAromaticityModelsData: Ignoring aromaticity model data for parameter name, $ParameterName, in $DataFile. It has already been loaded.\nLine: $Line..."; 4700 next LINE; 4701 } 4702 $ParameterNames{$ParameterName} = $ParameterName; 4703 4704 for $Index (1 .. $#LineWords) { 4705 $ModelName = $ColIndexToModelName{$Index}; 4706 $ParameterValue = $LineWords[$Index]; 4707 $AromaticityModelsDataMap{$ModelName}{$ParameterName} = $ParameterValue; 4708 } 4709 } 4710 close DATAFILE; 4711 } 4712 4713 # Process already loaded aromaticity model data... 4714 # 4715 sub _ProcessAromaticityModelsData { 4716 my($ParameterName, $ParameterValue, $ModelName, $NewParameterValue); 4717 4718 for $ModelName (keys %AromaticityModelsDataMap) { 4719 for $ParameterName (keys %{$AromaticityModelsDataMap{$ModelName}}) { 4720 $ParameterValue = $AromaticityModelsDataMap{$ModelName}{$ParameterName}; 4721 $ParameterValue =~ s/ //g; 4722 4723 VALUE: { 4724 if ($ParameterValue =~ /^Yes$/i) { 4725 $NewParameterValue = 1; 4726 last VALUE; 4727 } 4728 if ($ParameterValue =~ /^(NA|No)$/i) { 4729 $NewParameterValue = 0; 4730 last VALUE; 4731 } 4732 if ($ParameterValue =~ /^None$/i) { 4733 $NewParameterValue = ''; 4734 last VALUE; 4735 } 4736 $NewParameterValue = $ParameterValue; 4737 } 4738 $AromaticityModelsDataMap{$ModelName}{$ParameterName} = $NewParameterValue; 4739 4740 if ($ParameterName =~ /List/i) { 4741 # Setup a new parameter conatining a reference to a hash for the specified values... 4742 my($DataMapRefName, $DataValue, %DataMap); 4743 4744 $DataMapRefName = "${ParameterName}MapRef"; 4745 4746 %DataMap = (); 4747 for $DataValue (split /\,/, $NewParameterValue) { 4748 $DataMap{$DataValue} = $DataValue; 4749 } 4750 $AromaticityModelsDataMap{$ModelName}{$DataMapRefName} = \%DataMap; 4751 } 4752 } 4753 } 4754 } 4755 4756 # Is it a molecule object? 4757 sub _IsMolecule { 4758 my($Object) = @_; 4759 4760 return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0; 4761 } 4762