       1 package Bond;
       2 #
       3 # File: Bond.pm
       4 # Author: Manish Sud <msud@san.rr.com>
       5 #
       6 # Copyright (C) 2018 Manish Sud. All rights reserved.
       7 #
       8 # This file is part of MayaChemTools.
       9 #
      10 # MayaChemTools is free software; you can redistribute it and/or modify it under
      11 # the terms of the GNU Lesser General Public License as published by the Free
      12 # Software Foundation; either version 3 of the License, or (at your option) any
      13 # later version.
      14 #
      15 # MayaChemTools is distributed in the hope that it will be useful, but without
      16 # any warranty; without even the implied warranty of merchantability of fitness
      17 # for a particular purpose.  See the GNU Lesser General Public License for more
      18 # details.
      19 #
      20 # You should have received a copy of the GNU Lesser General Public License
      21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
      22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
      23 # Boston, MA, 02111-1307, USA.
      24 #
      25
      26 use strict;
      27 use Carp;
      28 use Exporter;
      29 use Storable ();
      30 use Scalar::Util ();
      31 use ObjectProperty;
      32
      33 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
      34
      35 @ISA = qw(ObjectProperty Exporter);
      36 @EXPORT = qw();
      37 @EXPORT_OK = qw();
      38
      39 %EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
      40
      41 # Setup class variables...
      42 my($ClassName, $ObjectID);
      43 _InitializeClass();
      44
      45 # Overload Perl functions...
      46 use overload '""' => 'StringifyBond';
      47
      48 # Class constructor...
      49 sub new {
      50   my($Class, %NamesAndValues) = @_;
      51
      52   # Initialize object...
      53   my $This = {};
      54   bless $This, ref($Class) || $Class;
      55   $This->_InitializeBond();
      56
      57   $This->_InitializeBondProperties(%NamesAndValues);
      58
      59   return $This;
      60 }
      61
      62 # Initialize object data...
      63 #
      64 sub _InitializeBond {
      65   my($This) = @_;
      66   my($ObjectID) = _GetNewObjectID();
      67
      68   # All other property names and values along with all Set/Get<PropertyName> methods
      69   # are implemented on-demand using ObjectProperty class.
      70   $This->{ID} = $ObjectID;
      71
      72   # Bond from and to atoms indicate begining and ending bond atoms...
      73   $This->{BondFromAtom} = undef;
      74   $This->{BondToAtom} = undef;
      75
      76   $This->{BondOrder} = '';
      77   $This->{BondType} = '';
      78   $This->{BondStereochemistry} = '';
      79 }
      80
      81 # Initialize class ...
      82 sub _InitializeClass {
      83   #Class name...
      84   $ClassName = __PACKAGE__;
      85
      86   # ID to keep track of objects...
      87   $ObjectID = 0;
      88 }
      89
      90 # Initialize bond properties...
      91 sub _InitializeBondProperties {
      92   my($This, %NamesAndValues) = @_;
      93
      94   my($Name, $Value, $MethodName);
      95   while (($Name, $Value) = each  %NamesAndValues) {
      96     $MethodName = "Set${Name}";
      97     $This->$MethodName($Value);
      98   }
      99
     100   if (!exists $NamesAndValues{'Atoms'}) {
     101     carp "Warning: ${ClassName}->new: Bond object instantiated without specifying atoms list...";
     102   }
     103   if (!exists $NamesAndValues{'BondOrder'}) {
     104     carp "Warning: ${ClassName}->new: Bond object instantiated without setting bond order...";
     105   }
     106   return $This;
     107 }
     108
     109 # Setup an explicit SetID method to block setting of ID by AUTOLOAD function...
     110 sub SetID {
     111   my($This, $Value) = @_;
     112
     113   carp "Warning: ${ClassName}->SetID: Object ID can't be changed: it's used for internal tracking...";
     114
     115   return $This;
     116 }
     117
     118 # Setup an explicit SetMolecule method to block setting of ID by AUTOLOAD function...
     119 sub SetMolecule {
     120   my($This, $Value) = @_;
     121
     122   carp "Warning: ${ClassName}->SetMolecule: Molecule property can't be changed: it's used for internal tracking...";
     123
     124   return $This;
     125 }
     126
     127 # Assign bond to  molecule...
     128 sub _SetMolecule {
     129   my($This, $Molecule) = @_;
     130
     131   $This->{Molecule} = $Molecule;
     132
     133   # Weaken the reference to disable increment of reference count; otherwise,
     134   # it it becomes a circular reference and destruction of Molecule object doesn't
     135   # get initiated which in turn disables destruction of bond object.
     136   #
     137   Scalar::Util::weaken($This->{Molecule});
     138
     139   return $This;
     140 }
     141
     142 # Set bond atoms...
     143 sub SetAtoms {
     144   my($This, @Values) = @_;
     145
     146   if (!@Values) {
     147     croak "Error: ${ClassName}->SetAtoms: No atoms specified...";
     148   }
     149
     150   my($FirstValue, $TypeOfFirstValue, $Atom1, $Atom2, $AtomID1, $AtomID2);
     151   $FirstValue = $Values[0];
     152   $TypeOfFirstValue = ref $FirstValue;
     153
     154   if ($TypeOfFirstValue =~ /^ARRAY/) {
     155     # Initialize using array refernce...
     156     if (@{$FirstValue} != 2) {
     157       croak "Warning: ${ClassName}->SetAtoms: Number of atoms specified in bond object is not equal to 2...";
     158     }
     159     ($Atom1, $Atom2) = @{$FirstValue};
     160   }
     161   else {
     162     # It's a list of values...
     163     if (@Values != 2) {
     164       croak "Warning: ${ClassName}->SetAtoms: Number of atoms specified in bond object is not equal to 2...";
     165     }
     166     ($Atom1, $Atom2) = @Values;
     167   }
     168
     169   $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID();
     170   if ($AtomID1 == $AtomID2) {
     171       croak "Warning: ${ClassName}->SetAtoms: Can't specify same atoms to create a bond...";
     172   }
     173
     174   $This->{BondFromAtom} = $Atom1;
     175   $This->{BondToAtom} = $Atom2;
     176
     177   return $This;
     178 }
     179
     180 # Get bond atoms as array. In scalar context, return number of atoms involved in
     181 # bond...
     182 #
     183 sub GetAtoms {
     184   my($This) = @_;
     185
     186   return wantarray ? ($This->{BondFromAtom}, $This->{BondToAtom}) : 2;
     187 }
     188
     189 # Get atom bonded to specified atom...
     190 sub GetBondedAtom {
     191   my($This, $Atom) = @_;
     192   my($Atom1, $Atom2, $AtomID1, $AtomID2, $AtomID);
     193
     194   ($Atom1, $Atom2) = $This->GetAtoms();
     195   $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID(); $AtomID = $Atom->GetID();
     196
     197   return ($AtomID1 == $AtomID) ? $Atom2 : (($AtomID2 == $AtomID) ? $Atom1 : undef) ;
     198 }
     199
     200 # Get common atom between two bonds...
     201 sub GetCommonAtom {
     202   my($This, $Other) = @_;
     203   my($Atom1, $Atom2, $AtomID1, $AtomID2, $OtherAtom1, $OtherAtom2, $OtherAtomID1, $OtherAtomID2);
     204
     205   ($Atom1, $Atom2) = $This->GetAtoms();
     206   $AtomID1 = $Atom1->GetID(); $AtomID2 = $Atom2->GetID();
     207
     208   ($OtherAtom1, $OtherAtom2) = $Other->GetAtoms();
     209   $OtherAtomID1 = $OtherAtom1->GetID(); $OtherAtomID2 = $OtherAtom2->GetID();
     210
     211   return ($AtomID1 == $OtherAtomID1 || $AtomID1 == $OtherAtomID2) ? $Atom1 : (($AtomID2 == $OtherAtomID1 || $AtomID2 == $OtherAtomID2) ? $Atom2 : undef) ;
     212 }
     213
     214 # Get bond from atom indicating begining atom of a bond...
     215 sub GetBondFromAtom {
     216   my($This) = @_;
     217
     218   return $This->{BondFromAtom};
     219 }
     220
     221 # Get begin bond atom...
     222 sub GetBondBeginAtom {
     223   my($This) = @_;
     224
     225   return $This->GetBondFromAtom();
     226 }
     227
     228 # Get bond to atom indicating ending atom of a bond...
     229 sub GetBondToAtom {
     230   my($This) = @_;
     231
     232   return $This->{BondToAtom};
     233 }
     234
     235 # Get begin bond atom...
     236 sub GetBondEndAtom {
     237   my($This) = @_;
     238
     239   return $This->GetBondToAtom();
     240 }
     241
     242 # Switch bond from and to atoms...
     243 sub SwitchBondFromAndToAtoms {
     244   my($This) = @_;
     245   my($FromAtom, $ToAtom);
     246
     247   ($FromAtom, $ToAtom) = $This->GetAtoms();
     248
     249   $This->{BondFromAtom} = $ToAtom;
     250   $This->{BondToAtom} = $FromAtom;
     251
     252   return $This;
     253 }
     254
     255 # Set bond order...
     256 #
     257 # Possible BondOrder are: 0 = None, 1 = Single, 1.5 = Aromatic, 2 = Double, 3 = Triple, 4 = Quadruple,
     258 # 5 = Quintuple, 6 = Sextuple, 7 = Septuple
     259 #
     260 # Possible BondType for different BondOrders are:
     261 #
     262 # 0 : None, Ionic, Unspecified
     263 # 1 : Single, Dative, Coordinate, SingleOrDouble, SingleOrAromatic, Tautomeric
     264 # 1.5 : Aromatic, Resonance, SingleOrAromatic, DoubleOrAromatic
     265 # 2 : Double, Tautomeric, SingleOrDouble, DoubleOrAromatic
     266 # 3 : Triple
     267 # 4 : Quadruple
     268 # 5 : Quintuple
     269 # 6 : Sextuple
     270 # 7 : Septuple
     271 #
     272 # Note: BondType Any is valid for all BondOrders.
     273 #
     274 # Possible BondStereochemistry values for different BondOrders are:
     275 #
     276 # 0 : None, Unspecified
     277 # 1 : Wedge, Up, Hash, Down, Wavy, WedgeOrHash, UpOrDown, Upward, Downward, None, Unspecified
     278 # 2 : Cis, Trans, Z, E, DoubleCross, CisOrTrans, None, Unspecified
     279 #
     280 # Notes:
     281 #   . BondType property is automatically assigned using default BondType values for
     282 #     specified BondOrder.
     283 #   . BondType values can also be explicit set.
     284 #   . To make bonds aromatic in a ring, explicitly set "Aromatic" property for bond/atoms and make sure
     285 #     appropriate BondOrder values are assigned.
     286 #   . Dative or coordinate bond types are treated as single bond types with explicit formal charge
     287 #     of + and - on first and second bond atoms.
     288 #
     289 sub SetBondOrder {
     290   my($This, $BondOrder) = @_;
     291
     292   if ($BondOrder !~ /^(0|1|1.5|2|3|4|5|6|7)$/) {
     293     croak "Error: ${ClassName}->SetBondOrder: BondOrder value $BondOrder is not valid. Supported values: 0, 1, 1.5, 2, 3, 4, 5, 6, 7";
     294   }
     295
     296   # Set bond order and type...
     297   my($BondType);
     298
     299   BONDORDER: {
     300       if ($BondOrder == 1) {$BondType = 'Single'; last BONDORDER; }
     301       if ($BondOrder == 1.5) {$BondType = 'Aromatic'; last BONDORDER; }
     302       if ($BondOrder == 2) {$BondType = 'Double'; last BONDORDER; }
     303       if ($BondOrder == 3) {$BondType = 'Triple'; last BONDORDER; }
     304       if ($BondOrder == 4) {$BondType = 'Quadruple'; last BONDORDER; }
     305       if ($BondOrder == 5) {$BondType = 'Quintuple'; last BONDORDER; }
     306       if ($BondOrder == 6) {$BondType = 'Sextuple'; last BONDORDER; }
     307       if ($BondOrder == 7) {$BondType = 'Septuple'; last BONDORDER; }
     308       $BondType = '';
     309       $BondOrder = 0;
     310   }
     311   $This->{BondType} = $BondType;
     312   $This->{BondOrder} = $BondOrder;
     313
     314   return $This;
     315
     316 }
     317
     318 # Set bond type for a specific bond...
     319 #
     320 sub SetBondType {
     321   my($This, $BondType) = @_;
     322
     323   if ($BondType !~ /^(None|Ionic|Unspecified|Single|Dative|Coordinate|SingleOrDouble|SingleOrAromatic|Aromatic|Tautomeric|Resonance|DoubleOrAromatic|Double|Triple|Quadruple|Any)$/i) {
     324     croak "Error: ${ClassName}->SetBondType: BondType value $BondType is not valid. Supported values: None, Ionic, Unspecified, Single, Dative, Coordinate, SingleOrDouble, SingleOrAromatic, Aromatic, Tautomeric, Resonance, DoubleOrAromatic, Double, Triple, Quadruple, Any...";
     325   }
     326
     327   # Make sure its a valid BondType value for BondOrder...
     328   my($BondOrder, $ValidBondType);
     329
     330   $ValidBondType = 0;
     331   $BondOrder = $This->{BondOrder};
     332
     333   BONDORDER: {
     334       if ($BondOrder == 0 && $BondType =~ /^(None|Ionic|Unspecified|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     335       if ($BondOrder == 1 && $BondType =~ /^(Single|Dative|Coordinate|SingleOrDouble|SingleOrAromatic|Tautomeric|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     336       if ($BondOrder == 1.5 && $BondType =~ /^(Aromatic|Resonance|SingleOrAromatic|DoubleOrAromatic|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     337       if ($BondOrder == 2 && $BondType =~ /^(Double|SingleOrDouble|DoubleOrAromatic|Tautomeric|Any)$/i ) {$ValidBondType = 1; last BONDORDER; }
     338       if ($BondOrder == 3 && $BondType =~ /^(Triple|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     339       if ($BondOrder == 4 && $BondType =~ /^(Quadruple|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     340       if ($BondOrder == 5 && $BondType =~ /^(Quintuple|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     341       if ($BondOrder == 6 && $BondType =~ /^(Sextuple|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     342       if ($BondOrder == 7 && $BondType =~ /^(Septuple|Any)$/i) {$ValidBondType = 1; last BONDORDER; }
     343       $ValidBondType = 0;
     344   }
     345
     346   if (!$ValidBondType) {
     347     carp "Warning: ${ClassName}->SetBondType: Assigning invalid BondType value, $BondType, for BondOrder $BondOrder...";
     348   }
     349
     350   $This->{BondType} = $BondType;
     351
     352   # Set explicit formal charge for atoms involved in Dative or coordinate bonds...
     353   if ($BondType =~ /^(Dative|Coordinate)$/i) {
     354     my($Atom1, $Atom2) = $This->GetAtoms();
     355     $Atom1->SetFormalCharge('1');
     356     $Atom2->SetFormalCharge('-1');
     357   }
     358
     359   return $This;
     360 }
     361
     362 # Set bond stereochemistry...
     363 #
     364 # Single bond stereochemistry:
     365 #
     366 # None, Unspecified: Not a stereo bond or unspecified
     367 #
     368 # Wedge, Up : Wedge end pointing up
     369 # Hash, Down: Wedge end pointing down
     370 # Wavy, WedgeOrHash, UpOrDown: Wedge end up or down
     371 #
     372 # Note: Wedge starts at begin atom of bond making wedge pointed end always at this
     373 #       atom.
     374 #
     375 # Upward: Single bond around cis/trans double bonds pointing upward
     376 # Downward: Single bond around cis/trans double bonds pointing upward
     377 #
     378 # Note: Upward/downward bonds start at atoms involved in cis/trans double bond.
     379 #
     380 # Double bond stereochemistry:
     381 #
     382 # None, Unspecified: Not a stereo bond or unspecified
     383 #
     384 # Z, cis: Similar groups on same side of double bond
     385 # E, trans: Similar groups on different side of double bond
     386 #
     387 # CisOrTrans, DoubleCross: cis or trans
     388 #
     389 sub SetBondStereochemistry {
     390   my($This, $BondStereochemistry) = @_;
     391
     392   if ($BondStereochemistry !~ /^(None|Unspecified|Wedge|Hash|Up|Down|Wavy|WedgeOrHash|UpOrDown|Upward|Downward|Cis|Trans|Z|E|DoubleCross|CisOrTrans)$/i) {
     393     croak "Error: ${ClassName}->SetBondStereochemistry: BondStereochemistry value $BondStereochemistry is not valid. Supported values: None, Unspecified, Wedge, Hash, Up, Down, Wavy, WedgeOrHash, UpOrDown, Upward, Downward, Cis, Trans, Z, E, DoubleCross, CisOrTrans...";
     394   }
     395
     396   # Make sure its a valid BondStereochemistry value for BondOrder...
     397   my($BondOrder, $ValidBondType);
     398
     399   $ValidBondType = 0;
     400   $BondOrder = $This->{BondOrder};
     401
     402   BONDORDER: {
     403       if ($BondOrder == 0 && $BondStereochemistry =~ /^(None|Unspecified)$/i) {$ValidBondType = 1; last BONDORDER; }
     404       if ($BondOrder == 1 && $BondStereochemistry =~ /^(Wedge|Hash|Up|Down|Wavy|WedgeOrHash|UpOrDown|Upward|Downward|None|Unspecified)$/i) {$ValidBondType = 1; last BONDORDER; }
     405       if ($BondOrder == 2 && $BondStereochemistry =~ /^(Cis|Trans|Z|E|DoubleCross|CisOrTrans|None|Unspecified)$/i ) {$ValidBondType = 1; last BONDORDER; }
     406       $ValidBondType = 0;
     407   }
     408
     409   if (!$ValidBondType) {
     410     carp "Warning: ${ClassName}->SetBondStereochemistry: Assigning invalid BondStereochemistry value, $BondStereochemistry, for BondOrder $BondOrder...";
     411   }
     412
     413   $This->{BondStereochemistry} = $BondStereochemistry;
     414
     415   return $This;
     416 }
     417
     418 # Is it a single bond?
     419 sub IsSingle {
     420   my($This) = @_;
     421
     422   return ($This->{BondOrder} == 1) ? 1 : 0;
     423 }
     424
     425 # Is it a double bond?
     426 sub IsDouble {
     427   my($This) = @_;
     428
     429   return ($This->{BondOrder} == 2) ? 1 : 0;
     430 }
     431
     432 # Is it a triple bond?
     433 sub IsTriple {
     434   my($This) = @_;
     435
     436   return ($This->{BondOrder} == 3) ? 1 : 0;
     437 }
     438
     439 # Is it a quadruple bond?
     440 sub IsQuadruple {
     441   my($This) = @_;
     442
     443   return ($This->{BondOrder} == 4) ? 1 : 0;
     444 }
     445
     446 # Is aromatic property set for the bond?
     447 sub IsAromatic {
     448   my($This) = @_;
     449   my($Aromatic);
     450
     451   $Aromatic = $This->GetAromatic();
     452
     453   return ((defined($Aromatic) && $Aromatic) || $This->{BondOrder} == 1.5) ? 1 : 0;
     454 }
     455
     456 # Is it a quintuple bond?
     457 sub IsQuintuple {
     458   my($This) = @_;
     459
     460   return ($This->{BondOrder} == 5) ? 1 : 0;
     461 }
     462
     463 # Is it a sextuple bond?
     464 sub IsSextuple {
     465   my($This) = @_;
     466
     467   return ($This->{BondOrder} == 6) ? 1 : 0;
     468 }
     469
     470 # Is bond type specified?
     471 sub IsBondTypeSpecified {
     472   my($This) = @_;
     473
     474   return ($This->{BondType} && $This->{BondType} !~ /^(None|Unspecified)$/i) ? 1 : 0;
     475 }
     476
     477 # Is it a dative or coordinate bond?
     478 sub IsDative {
     479   my($This) = @_;
     480
     481   return ($This->{BondType} =~ /^(Dative|Coordinate)$/i) ? 1 : 0;
     482 }
     483
     484 # Is it a dative or coordinate bond?
     485 sub IsCoordinate {
     486   my($This) = @_;
     487
     488   return $This->IsDative();
     489 }
     490
     491 # Is it a ionic bond?
     492 sub IsIonic {
     493   my($This) = @_;
     494
     495   return ($This->{BondType} =~ /^Ionic$/i) ? 1 : 0;
     496 }
     497
     498 # Is it a tautomeric bond?
     499 sub IsTautomeric {
     500   my($This) = @_;
     501
     502   return ($This->{BondType} =~ /^Tautomeric$/i) ? 1 : 0;
     503 }
     504
     505 # Is bond stereochemistry specified?
     506 sub IsBondStereochemistrySpecified {
     507   my($This) = @_;
     508
     509   return ($This->{BondStereochemistry} && $This->{BondStereochemistry} !~ /^(None|Unspecified)$/i) ? 1 : 0;
     510 }
     511
     512 # Is it a Wedge or Up single bond?
     513 sub IsWedge {
     514   my($This) = @_;
     515
     516   return $This->IsUp();
     517 }
     518
     519 # Is it a Wedge or Up single bond?
     520 sub IsUp {
     521   my($This) = @_;
     522
     523   return ($This->{BondStereochemistry} =~ /^(Wedge|Up)$/i) ? 1 : 0;
     524 }
     525
     526 # Is it a Hash or Down single bond?
     527 sub IsHash {
     528   my($This) = @_;
     529
     530   return $This->IsDown();
     531 }
     532
     533 # Is it a Hash or Down single bond?
     534 sub IsDown {
     535   my($This) = @_;
     536
     537   return ($This->{BondStereochemistry} =~ /^(Hash|Down)$/i) ? 1 : 0;
     538 }
     539
     540 # Is it a Wavy, WedgeOrHash or UpOrDown single bond?
     541 sub IsWedgeOrHash {
     542   my($This) = @_;
     543
     544   return $This->IsUpOrDown();
     545 }
     546
     547 # Is it a Wavy, WedgeOrHash or UpOrDown single bond?
     548 sub IsUpOrDown {
     549   my($This) = @_;
     550
     551   return ($This->{BondStereochemistry} =~ /^(Wavy|WedgeOrHash|UpOrDown)$/i) ? 1 : 0;
     552 }
     553
     554 # Is it a upward single bond?
     555 sub IsUpward {
     556   my($This) = @_;
     557
     558   return ($This->{BondStereochemistry} =~ /^Upward$/i) ? 1 : 0;
     559 }
     560
     561 # Is it a Downward single bond?
     562 sub IsDownward {
     563   my($This) = @_;
     564
     565   return ($This->{BondStereochemistry} =~ /^Downward$/i) ? 1 : 0;
     566 }
     567 # Is it a cis or Z double bond?
     568 sub IsCis {
     569   my($This) = @_;
     570
     571   return ($This->{BondStereochemistry} =~ /^(Cis|Z)$/i) ? 1 : 0;
     572 }
     573
     574 # Is it a trans or E double bond?
     575 sub IsTrans {
     576   my($This) = @_;
     577
     578   return ($This->{BondStereochemistry} =~ /^(Trans|E)$/i) ? 1 : 0;
     579 }
     580
     581 # Is it a cis or trans double bond?
     582 sub IsCisOrTrans {
     583   my($This) = @_;
     584
     585   return ($This->{BondStereochemistry} =~ /^(CisOrTrans|DoubleCross)$/i) ? 1 : 0;
     586 }
     587
     588 # Delete bond...
     589 sub DeleteBond {
     590   my($This) = @_;
     591
     592   # Is this atom in a molecule?
     593   if (!$This->HasProperty('Molecule')) {
     594     # Nothing to do...
     595     return $This;
     596   }
     597   my($Molecule);
     598   $Molecule = $This->GetProperty('Molecule');
     599   $Molecule->DeleteBond($This);
     600
     601   return $This;
     602 }
     603
     604 # Copy bond and all its associated data...
     605 sub Copy {
     606   my($This) = @_;
     607   my($Bond);
     608
     609   $Bond = Storable::dclone($This);
     610
     611   return $Bond;
     612 }
     613
     614 # Is bond in a ring?
     615 #
     616 sub IsInRing {
     617   my($This) = @_;
     618
     619   # Is this bond in a molecule?
     620   if (!$This->HasProperty('Molecule')) {
     621     return undef;
     622   }
     623   my($Molecule);
     624   $Molecule = $This->GetProperty('Molecule');
     625
     626   return $Molecule->_IsBondInRing($This);
     627 }
     628
     629 # Is bond not in a ring?
     630 #
     631 sub IsNotInRing {
     632   my($This) = @_;
     633
     634   # Is this bond in a molecule?
     635   if (!$This->HasProperty('Molecule')) {
     636     return undef;
     637   }
     638   my($Molecule);
     639   $Molecule = $This->GetProperty('Molecule');
     640
     641   return $Molecule->_IsBondNotInRing($This);
     642 }
     643
     644 # Is bond only in one ring?
     645 #
     646 sub IsOnlyInOneRing {
     647   my($This) = @_;
     648
     649   # Is this bond in a molecule?
     650   if (!$This->HasProperty('Molecule')) {
     651     return undef;
     652   }
     653   my($Molecule);
     654   $Molecule = $This->GetProperty('Molecule');
     655
     656   return $Molecule->_IsBondInOnlyOneRing($This);
     657 }
     658
     659 # Is bond in a ring of specific size?
     660 #
     661 sub IsInRingOfSize {
     662   my($This, $RingSize) = @_;
     663
     664   # Is this bond in a molecule?
     665   if (!$This->HasProperty('Molecule')) {
     666     return undef;
     667   }
     668   my($Molecule);
     669   $Molecule = $This->GetProperty('Molecule');
     670
     671   return $Molecule->_IsBondInRingOfSize($This, $RingSize);
     672 }
     673
     674 # Get size of smallest ring containing the bond...
     675 #
     676 sub GetSizeOfSmallestRing {
     677   my($This) = @_;
     678
     679   # Is this bond in a molecule?
     680   if (!$This->HasProperty('Molecule')) {
     681     return undef;
     682   }
     683   my($Molecule);
     684   $Molecule = $This->GetProperty('Molecule');
     685
     686   return $Molecule->_GetSizeOfSmallestBondRing($This);
     687 }
     688
     689 # Get size of largest ring containing the bond...
     690 #
     691 sub GetSizeOfLargestRing {
     692   my($This) = @_;
     693
     694   # Is this bond in a molecule?
     695   if (!$This->HasProperty('Molecule')) {
     696     return undef;
     697   }
     698   my($Molecule);
     699   $Molecule = $This->GetProperty('Molecule');
     700
     701   return $Molecule->_GetSizeOfLargestBondRing($This);
     702 }
     703
     704 # Get number of  rings containing the bond...
     705 #
     706 sub GetNumOfRings {
     707   my($This) = @_;
     708
     709   # Is this bond in a molecule?
     710   if (!$This->HasProperty('Molecule')) {
     711     return undef;
     712   }
     713   my($Molecule);
     714   $Molecule = $This->GetProperty('Molecule');
     715
     716   return $Molecule->_GetNumOfBondRings($This);
     717 }
     718
     719 # Get number of  rings with odd size containing the bond...
     720 #
     721 sub GetNumOfRingsWithOddSize {
     722   my($This) = @_;
     723
     724   # Is this bond in a molecule?
     725   if (!$This->HasProperty('Molecule')) {
     726     return undef;
     727   }
     728   my($Molecule);
     729   $Molecule = $This->GetProperty('Molecule');
     730
     731   return $Molecule->_GetNumOfBondRingsWithOddSize($This);
     732 }
     733
     734 # Get number of  rings with even size containing the bond...
     735 #
     736 sub GetNumOfRingsWithEvenSize {
     737   my($This) = @_;
     738
     739   # Is this bond in a molecule?
     740   if (!$This->HasProperty('Molecule')) {
     741     return undef;
     742   }
     743   my($Molecule);
     744   $Molecule = $This->GetProperty('Molecule');
     745
     746   return $Molecule->_GetNumOfBondRingsWithEvenSize($This);
     747 }
     748
     749 # Get number of  rings with specified size containing the bond...
     750 #
     751 sub GetNumOfRingsWithSize {
     752   my($This, $RingSize) = @_;
     753
     754   # Is this bond in a molecule?
     755   if (!$This->HasProperty('Molecule')) {
     756     return undef;
     757   }
     758   my($Molecule);
     759   $Molecule = $This->GetProperty('Molecule');
     760
     761   return $Molecule->_GetNumOfBondRingsWithSize($This, $RingSize);
     762 }
     763
     764 # Get number of  rings with size less than specified containing the bond...
     765 #
     766 sub GetNumOfRingsWithSizeLessThan {
     767   my($This, $RingSize) = @_;
     768
     769   # Is this bond in a molecule?
     770   if (!$This->HasProperty('Molecule')) {
     771     return undef;
     772   }
     773   my($Molecule);
     774   $Molecule = $This->GetProperty('Molecule');
     775
     776   return $Molecule->_GetNumOfBondRingsWithSizeLessThan($This, $RingSize);
     777 }
     778
     779 # Get number of  rings with size greater than specified size containing the bond...
     780 #
     781 sub GetNumOfRingsWithSizeGreaterThan {
     782   my($This, $RingSize) = @_;
     783
     784   # Is this bond in a molecule?
     785   if (!$This->HasProperty('Molecule')) {
     786     return undef;
     787   }
     788   my($Molecule);
     789   $Molecule = $This->GetProperty('Molecule');
     790
     791   return $Molecule->_GetNumOfBondRingsWithSizeGreaterThan($This, $RingSize);
     792 }
     793
     794 # Get all rings as an array of references to arrays containing ring atoms...
     795 #
     796 sub GetRings {
     797   my($This) = @_;
     798
     799   # Is this bond in a molecule?
     800   if (!$This->HasProperty('Molecule')) {
     801     return undef;
     802   }
     803   my($Molecule);
     804   $Molecule = $This->GetProperty('Molecule');
     805
     806   return $Molecule->_GetBondRings($This);
     807 }
     808
     809 # Get smallest ring as an array containing ring atoms...
     810 #
     811 sub GetSmallestRing {
     812   my($This) = @_;
     813
     814   # Is this bond in a molecule?
     815   if (!$This->HasProperty('Molecule')) {
     816     return undef;
     817   }
     818   my($Molecule);
     819   $Molecule = $This->GetProperty('Molecule');
     820
     821   return $Molecule->_GetSmallestBondRing($This);
     822 }
     823
     824 # Get largest ring as an array containing ring atoms...
     825 #
     826 sub GetLargestRing {
     827   my($This) = @_;
     828
     829   # Is this bond in a molecule?
     830   if (!$This->HasProperty('Molecule')) {
     831     return undef;
     832   }
     833   my($Molecule);
     834   $Molecule = $This->GetProperty('Molecule');
     835
     836   return $Molecule->_GetLargestBondRing($This);
     837 }
     838
     839 # Get odd size rings an array of references to arrays containing ring atoms...
     840 #
     841 sub GetRingsWithOddSize {
     842   my($This) = @_;
     843
     844   # Is this bond in a molecule?
     845   if (!$This->HasProperty('Molecule')) {
     846     return undef;
     847   }
     848   my($Molecule);
     849   $Molecule = $This->GetProperty('Molecule');
     850
     851   return $Molecule->_GetBondRingsWithOddSize($This);
     852 }
     853
     854 # Get even size rings an array of references to arrays containing ring atoms...
     855 #
     856 sub GetRingsWithEvenSize {
     857   my($This) = @_;
     858
     859   # Is this bond in a molecule?
     860   if (!$This->HasProperty('Molecule')) {
     861     return undef;
     862   }
     863   my($Molecule);
     864   $Molecule = $This->GetProperty('Molecule');
     865
     866   return $Molecule->_GetBondRingsWithEvenSize($This);
     867 }
     868
     869 # Get rings with specified size  an array of references to arrays containing ring atoms...
     870 #
     871 sub GetRingsWithSize {
     872   my($This, $RingSize) = @_;
     873
     874   # Is this bond in a molecule?
     875   if (!$This->HasProperty('Molecule')) {
     876     return undef;
     877   }
     878   my($Molecule);
     879   $Molecule = $This->GetProperty('Molecule');
     880
     881   return $Molecule->_GetBondRingsWithSize($This, $RingSize);
     882 }
     883
     884 # Get rings with size less than specfied size as an array of references to arrays containing ring atoms...
     885 #
     886 sub GetRingsWithSizeLessThan {
     887   my($This, $RingSize) = @_;
     888
     889   # Is this bond in a molecule?
     890   if (!$This->HasProperty('Molecule')) {
     891     return undef;
     892   }
     893   my($Molecule);
     894   $Molecule = $This->GetProperty('Molecule');
     895
     896   return $Molecule->_GetBondRingsWithSizeLessThan($This, $RingSize);
     897 }
     898
     899 # Get rings with size greater than specfied size as an array of references to arrays containing ring atoms...
     900 #
     901 sub GetRingsWithSizeGreaterThan {
     902   my($This, $RingSize) = @_;
     903
     904   # Is this bond in a molecule?
     905   if (!$This->HasProperty('Molecule')) {
     906     return undef;
     907   }
     908   my($Molecule);
     909   $Molecule = $This->GetProperty('Molecule');
     910
     911   return $Molecule->_GetBondRingsWithSizeGreaterThan($This, $RingSize);
     912 }
     913
     914 # Get next object ID...
     915 sub _GetNewObjectID {
     916   $ObjectID++;
     917   return $ObjectID;
     918 }
     919
     920 # Return a string containing bond and other properties...
     921 sub StringifyBond {
     922   my($This) = @_;
     923   my($BondString, $ID, $BondOrder, $BondType, $Stereochemistry, $AtomsString, $RingBond, $AromaticBond, $NumOfRings, $Atom1, $Atom2);
     924
     925   $ID = $This->GetID();
     926   $BondOrder = $This->GetBondOrder();
     927   if (!defined $BondOrder) {
     928     $BondOrder = "undefined";
     929   }
     930   $BondType = $This->GetBondType();
     931   if (!defined $BondOrder) {
     932     $BondType = "undefined";
     933   }
     934   if (defined($BondOrder) && $BondOrder == 2) {
     935     $Stereochemistry = $This->GetStereochemistry();
     936     if (!defined $Stereochemistry) {
     937       $Stereochemistry = "undefined";
     938     }
     939   }
     940   $RingBond = $This->IsInRing();
     941   if (defined $RingBond) {
     942     $RingBond = $RingBond  ? 'Yes' : 'No';
     943     $NumOfRings = $This->GetNumOfRings();
     944   }
     945   else {
     946     $RingBond = 'undefined';
     947     $NumOfRings = 'undefined';
     948   }
     949
     950   $AromaticBond = $This->GetAromatic();
     951   if (defined $AromaticBond) {
     952     $AromaticBond = $AromaticBond  ? 'Yes' : 'No';
     953   }
     954   else {
     955     $AromaticBond = 'undefined';
     956   }
     957
     958   ($Atom1, $Atom2) = $This->GetAtoms();
     959   $AtomsString = "Atoms: undefined";
     960   if (defined($Atom1) && defined($Atom2)) {
     961     my($Atom, $AtomID, $AtomCount, $AtomName, $AtomSymbol, $AtomicNumber, @BondAtoms);
     962     @BondAtoms = ();
     963     push @BondAtoms, ($Atom1, $Atom2);
     964     $AtomCount = 0;
     965     $AtomsString = "";
     966     for $Atom (@BondAtoms) {
     967       $AtomCount++;
     968       $AtomID = $Atom->GetID();
     969       $AtomName = $Atom->GetName();
     970       $AtomSymbol = $Atom->GetAtomSymbol();
     971       $AtomicNumber = $Atom->GetAtomicNumber();
     972       if ($AtomCount == 1) {
     973         $AtomsString .= "FirstAtom:";
     974       }
     975       else {
     976         $AtomsString .= "; SecondAtom:";
     977       }
     978       $AtomsString .= " [ ID: $AtomID; Name: \"$AtomName\"; AtomSymbol: \"$AtomSymbol\"; AtomicNumber: $AtomicNumber ]";
     979     }
     980   }
     981   $BondString = "Bond: ID: $ID; BondOrder: $BondOrder; BondType: $BondType; RingBond: $RingBond; NumOfBondRings: $NumOfRings; AromaticBond: $AromaticBond;";
     982   if (defined($BondOrder) && $BondOrder == 2) {
     983   $BondString .= " Stereochemistry: $Stereochemistry;";
     984   }
     985   $BondString .= " $AtomsString";
     986
     987   return $BondString;
     988 }
     989