Chemistry-Ring-0.21/0000775000200400020040000000000014347026546014173 5ustar andriusandriusChemistry-Ring-0.21/dist.ini0000644000200400020040000000076514347026546015645 0ustar andriusandriusname = Chemistry-Ring author = Ivan Tubert-Brohman license = Perl_5 copyright_holder = Ivan Tubert-Brohman copyright_year = 2009 version = 0.21 [@Filter] -bundle = @Basic -remove = License -remove = Readme [AutoMetaResources] homepage = https://search.cpan.org/dist/%{dist} repository.github = user:perlmol [MetaJSON] [OurPkgVersion] [Prereqs] Chemistry::Mol = 0.24 Statistics::Regression = 0.15 [Prereqs / Test] -phase = test Chemistry::File::SMILES = 0 Test::Simple = 0 Chemistry-Ring-0.21/Changes0000644000200400020040000000234414347026546015467 0ustar andriusandriusRevision history for Perl extension Chemistry::Ring 0.21 Dec 16 2022 - Fixed incompatibility with Statistics::Regression v0.52. - Switched to Dist::Zilla. - Adjusted URLs in documentation, as the source has been hosted on GitHub. - New co-maintainer Andrius Merkys . 0.20 May 10 2009 - Silence some warnings. - Minor changes by Liliana Felix Avila for compatibility with Chemistry::Artificial. 0.19 Mar 29 2005 - Fixed memory leak. - Added 'ring/rings' attribute to molecule on aromatize_mol. 0.18 Aug 12 2004 - Fixed non-deterministic ring order test failure. 0.17 Aug 11 2004 - Fixed bug where find_rings didn't find all the rings in disconnected structures. 0.16 Aug 2 2004 - Make it warnings-clean. - ring/rings attribute added by aromatize_mol 0.15 Jun 30 2004 - Find the "almost SSSR" in the molecule. - Improved aromatize_mol. 0.11 Jun 17 2004 - New subroutine in Chemistry::Ring: aromatize_mol - Added some tests. - Fixed mirror bug in ring_find. - Fixed is_aromatic to use implicit hydrogens. 0.10 Jun 16 2004 - First CPAN release Chemistry-Ring-0.21/README0000644000200400020040000000251714347026546015056 0ustar andriusandriusChemistry::Ring version 0.21 ============================ The Chemistry::Ring module provides some basic methods for representing a ring. A ring is a subclass of molecule, because it has atoms and bonds. Besides that, it has some useful geometric methods for finding the centroid and the ring plane, and methods for aromaticity detection. The Chemistry::Ring::Find module implements a breadth-first ring finding algorithm, and it can find all rings that contain a given atom or bond, the Smallest Set of Smallest Rings (SSSR), or the "almost SSSR", which is an unambiguous set of rings for cases such as cubane. CHANGES SINCE VERSION 0.20 - Fixed incompatibility with Statistics::Regression v0.52. - Switched to Dist::Zilla. - Adjusted URLs in documentation, as the source has been hosted on GitHub. - New co-maintainer Andrius Merkys . INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES This module requires these other modules and libraries: Chemistry::Mol 0.24 Statistics::Regression 0.15 COPYRIGHT AND LICENSE Copyright (C) 2009 Ivan Tubert-Brohman This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Chemistry-Ring-0.21/MANIFEST0000644000200400020040000000133114347026546015320 0ustar andriusandrius# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012. Changes MANIFEST MANIFEST.SKIP META.json META.yml Makefile.PL README dist.ini lib/Chemistry/Ring.pm lib/Chemistry/Ring/Find.pm t/find_ring.t t/find_rings.t t/mem.t t/plane.t t/pod.t t/ring.t t/ring_find.t t/rings/benzene_kekule.ring t/rings/byciclic.ring t/rings/byciclic_bond.ring t/rings/byciclic_bond2.ring t/rings/contained.ring t/rings/contained2.ring t/rings/cyclobutadiene.ring t/rings/cycloheptatriene.ring t/rings/cyclopentadiene.ring t/rings/furan.ring t/rings/mirror.ring t/rings/pyridine.ring t/rings/pyrrole.ring t/rings2/biphenyl.ring t/rings2/cubane.ring t/rings2/cubane_sssr.ring t/rings2/disconnected.ring t/rings2/norbornane.ring Chemistry-Ring-0.21/META.yml0000644000200400020040000000135014347026546015441 0ustar andriusandrius--- abstract: 'Represent a ring as a substructure of a molecule' author: - 'Ivan Tubert-Brohman ' build_requires: Chemistry::File::SMILES: '0' Test::Simple: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Chemistry-Ring requires: Chemistry::Mol: '0.24' Statistics::Regression: '0.15' resources: homepage: https://search.cpan.org/dist/Chemistry-Ring repository: git://github.com/perlmol/chemistry-ring.git version: '0.21' x_generated_by_perl: v5.30.0 x_serialization_backend: 'YAML::Tiny version 1.73' Chemistry-Ring-0.21/t/0000775000200400020040000000000014347026546014436 5ustar andriusandriusChemistry-Ring-0.21/t/find_rings.t0000644000200400020040000000241614347026546016746 0ustar andriusandriususe Test::More; use Chemistry::Mol; use Chemistry::Ring::Find ':all'; use strict; my @files = glob "t/rings2/*.ring"; eval "use Chemistry::File::SMILES"; if ($@) { plan skip_all => "You don't have Chemistry::File::SMILES installed"; } else { plan tests => 0 + @files; } for my $file (@files) { open F, $file or die "couldn't open $file: $!\n"; my ($smiles, $options, @expected_rings) = map { /: ([^\n\r]*)/g } ; my ($mol, $patt); Chemistry::Atom->reset_id; Chemistry::Bond->reset_id; $mol = Chemistry::Mol->parse($smiles, format => 'smiles'); my %opts = split " ", $options; my @rings = find_rings($mol, %opts); #use List::Util 'shuffle'; @rings = shuffle @rings; @rings = map { $_->[1] } sort { $a->[0] cmp $b->[0] } map { [ join(" ", $_->atoms) => $_ ] } @rings; my @got_rings; for my $ring (@rings) { my @atoms = $ring->atoms; my @bonds = $ring->bonds; my $aromatic = $ring->is_aromatic; push @got_rings, "atoms(@atoms); bonds(@bonds); aromatic($aromatic)"; } if ($opts{sssr}) { is(scalar @got_rings, scalar @expected_rings, "$file: $smiles"); } else { is_deeply(\@got_rings, \@expected_rings, "$file: $smiles"); } } Chemistry-Ring-0.21/t/mem.t0000644000200400020040000000252614347026546015404 0ustar andriusandriususe Test::More; # These tests try to make sure that objects are destroyed when they # fall out of scope; these requires avoiding circular strong references use strict; use warnings; use Chemistry::Ring; plan tests => 10; #plan 'no_plan'; my $dead_atoms = 0; my $dead_bonds = 0; my $dead_mols = 0; my $dead_rings = 0; my $badatom; { # make a cyclopropane my $mol = Chemistry::Mol->new; $mol->new_atom(symbol => 'C'); $mol->new_atom(symbol => 'C'); $mol->new_atom(symbol => 'C'); $mol->new_bond(atoms => [$mol->atoms(1,2)]); $mol->new_bond(atoms => [$mol->atoms(2,3)]); $mol->new_bond(atoms => [$mol->atoms(3,1)]); isa_ok( $mol, 'Chemistry::Mol' ); is( scalar $mol->atoms, 3, 'atoms before'); Chemistry::Ring::aromatize_mol($mol); is( $dead_atoms, 0, "before gc - atoms" ); is( $dead_bonds, 0, "before gc - bonds" ); is( $dead_mols, 0, "before gc - mols" ); is( $dead_rings, 0, "before gc - rings" ); } is( $dead_atoms, 3, "after gc - atoms" ); is( $dead_bonds, 3, "after gc - bonds" ); is( $dead_mols, 1, "after gc - mols" ); is( $dead_rings, 1, "after gc - rings" ); sub Chemistry::Mol::DESTROY { $dead_mols++ } sub Chemistry::Atom::DESTROY { $dead_atoms++ } sub Chemistry::Bond::DESTROY { $dead_bonds++ } sub Chemistry::Ring::DESTROY { $dead_rings++ } Chemistry-Ring-0.21/t/ring.t0000644000200400020040000000010014347026546015547 0ustar andriusandriususe Test::More tests => 1; BEGIN { use_ok('Chemistry::Ring') }; Chemistry-Ring-0.21/t/rings2/0000775000200400020040000000000014347026546015642 5ustar andriusandriusChemistry-Ring-0.21/t/rings2/biphenyl.ring0000644000200400020040000000026314347026546020334 0ustar andriusandriusMol: c1ccccc1c1ccccc1 Options: Ring: atoms(a1 a6 a5 a4 a3 a2); bonds(b6 b5 b4 b3 b2 b1); aromatic(1) Ring: atoms(a7 a12 a11 a10 a9 a8); bonds(b13 b12 b11 b10 b9 b8); aromatic(1) Chemistry-Ring-0.21/t/rings2/cubane_sssr.ring0000644000200400020040000000051614347026546021032 0ustar andriusandriusMol: C12C3C4C1C5C4C3C25 Options: sssr 1 Ring: atoms(a1 a4 a3 a2); bonds(b4 b3 b2 b1); aromatic(0) Ring: atoms(a2 a7 a6 a3); bonds(b9 b8 b7 b2); aromatic(0) Ring: atoms(a3 a6 a5 a4); bonds(b7 b6 b5 b3); aromatic(0) Ring: atoms(a5 a8 a1 a4); bonds(b12 b11 b4 b5); aromatic(0) Ring: atoms(a5 a8 a7 a6); bonds(b12 b10 b8 b6); aromatic(0) Chemistry-Ring-0.21/t/rings2/disconnected.ring0000644000200400020040000000011314347026546021156 0ustar andriusandriusMol: C.C1CC1 Options: Ring: atoms(a2 a3 a4); bonds(b1 b2 b3); aromatic(0) Chemistry-Ring-0.21/t/rings2/norbornane.ring0000644000200400020040000000023514347026546020664 0ustar andriusandriusMol: C12CC(CC1)CC2 Options: Ring: atoms(a1 a2 a3 a4 a5); bonds(b1 b2 b3 b4 b5); aromatic(0) Ring: atoms(a1 a2 a3 a6 a7); bonds(b1 b2 b6 b7 b8); aromatic(0) Chemistry-Ring-0.21/t/rings2/cubane.ring0000644000200400020040000000060414347026546017756 0ustar andriusandriusMol: C12C3C4C1C5C4C3C25 Options: Ring: atoms(a1 a4 a3 a2); bonds(b4 b3 b2 b1); aromatic(0) Ring: atoms(a1 a8 a7 a2); bonds(b11 b10 b9 b1); aromatic(0) Ring: atoms(a2 a7 a6 a3); bonds(b9 b8 b7 b2); aromatic(0) Ring: atoms(a3 a6 a5 a4); bonds(b7 b6 b5 b3); aromatic(0) Ring: atoms(a5 a8 a1 a4); bonds(b12 b11 b4 b5); aromatic(0) Ring: atoms(a5 a8 a7 a6); bonds(b12 b10 b8 b6); aromatic(0) Chemistry-Ring-0.21/t/find_ring.t0000644000200400020040000000177214347026546016567 0ustar andriusandriususe Test::More; use Chemistry::Mol; use Chemistry::Ring::Find ':all'; use strict; my @files = glob "t/rings/*.ring"; eval "use Chemistry::File::SMILES"; if ($@) { plan skip_all => "You don't have Chemistry::File::SMILES installed"; } else { plan tests => 0 + @files; } for my $file (@files) { open F, $file or die "couldn't open $file: $!\n"; my ($smiles, $origin, $options, @expected_rings) = map { /: ([^\n\r]*)/g } ; my ($mol, $patt); Chemistry::Atom->reset_id; Chemistry::Bond->reset_id; $mol = Chemistry::Mol->parse($smiles, format => 'smiles'); my %opts = split " ", $options; my @rings = find_ring($mol->by_id($origin), %opts); my @got_rings; for my $ring (@rings) { my @atoms = $ring->atoms; my @bonds = $ring->bonds; my $aromatic = $ring->is_aromatic; push @got_rings, "atoms(@atoms); bonds(@bonds); aromatic($aromatic)"; } is_deeply(\@got_rings, \@expected_rings, "$file: $smiles"); } Chemistry-Ring-0.21/t/ring_find.t0000644000200400020040000000010014347026546016547 0ustar andriusandriususe Test::More tests => 1; BEGIN { use_ok('Chemistry::Ring') }; Chemistry-Ring-0.21/t/plane.t0000644000200400020040000000133314347026546015720 0ustar andriusandriususe Test::More; use strict; use warnings; use Chemistry::Mol; use Chemistry::Ring; plan tests => 2; # make a cyclobutane my $mol = Chemistry::Mol->new; $mol->new_atom(symbol => 'C', coords => [-1, -1, 0]); $mol->new_atom(symbol => 'C', coords => [-1, 1, 0]); $mol->new_atom(symbol => 'C', coords => [ 1, 1, 0]); $mol->new_atom(symbol => 'C', coords => [ 1, -1, 0]); $mol->new_bond(atoms => [$mol->atoms(1,2)]); $mol->new_bond(atoms => [$mol->atoms(2,3)]); $mol->new_bond(atoms => [$mol->atoms(3,4)]); $mol->new_bond(atoms => [$mol->atoms(4,1)]); isa_ok( $mol, 'Chemistry::Mol' ); my $ring = Chemistry::Ring->new; $ring->add_atom( $_ ) for $mol->atoms(1..4); my( $normal, $distance ) = $ring->plane; ok( $distance < 1e-6 ); Chemistry-Ring-0.21/t/rings/0000775000200400020040000000000014347026546015560 5ustar andriusandriusChemistry-Ring-0.21/t/rings/furan.ring0000644000200400020040000000014414347026546017551 0ustar andriusandriusMol: C1=CC=CO1 Origin: a1 Options: Ring: atoms(a1 a2 a3 a4 a5); bonds(b1 b2 b3 b4 b5); aromatic(1) Chemistry-Ring-0.21/t/rings/cycloheptatriene.ring0000644000200400020040000000016314347026546022001 0ustar andriusandriusMol: C1=CC=CC=CC1 Origin: a1 Options: Ring: atoms(a1 a2 a3 a4 a5 a6 a7); bonds(b1 b2 b3 b4 b5 b6 b7); aromatic(0) Chemistry-Ring-0.21/t/rings/cyclobutadiene.ring0000644000200400020040000000013514347026546021430 0ustar andriusandriusMol: C1=CC=C1 Origin: a1 Options: Ring: atoms(a1 a4 a3 a2); bonds(b4 b3 b2 b1); aromatic(0) Chemistry-Ring-0.21/t/rings/pyridine.ring0000644000200400020040000000015414347026546020262 0ustar andriusandriusMol: C1=CC=CC=N1 Origin: a1 Options: Ring: atoms(a1 a6 a5 a4 a3 a2); bonds(b6 b5 b4 b3 b2 b1); aromatic(1) Chemistry-Ring-0.21/t/rings/cyclopentadiene.ring0000644000200400020040000000014414347026546021604 0ustar andriusandriusMol: C1=CC=CC1 Origin: a1 Options: Ring: atoms(a1 a2 a3 a4 a5); bonds(b1 b2 b3 b4 b5); aromatic(0) Chemistry-Ring-0.21/t/rings/byciclic_bond.ring0000644000200400020040000000022014347026546021214 0ustar andriusandriusMol: C12CC1C2 Origin: b3 Options: all 1 Ring: atoms(a1 a2 a3); bonds(b1 b2 b3); aromatic(0) Ring: atoms(a1 a4 a3); bonds(b5 b4 b3); aromatic(0) Chemistry-Ring-0.21/t/rings/contained2.ring0000644000200400020040000000015014347026546020461 0ustar andriusandriusMol: C12CC1C2 Origin: a2 Options: min 4 all 1 Ring: atoms(a2 a3 a4 a1); bonds(b2 b4 b5 b1); aromatic(0) Chemistry-Ring-0.21/t/rings/byciclic.ring0000644000200400020040000000022014347026546020212 0ustar andriusandriusMol: C12CC1C2 Origin: a1 Options: all 1 Ring: atoms(a1 a2 a3); bonds(b1 b2 b3); aromatic(0) Ring: atoms(a1 a4 a3); bonds(b5 b4 b3); aromatic(0) Chemistry-Ring-0.21/t/rings/pyrrole.ring0000644000200400020040000000014414347026546020132 0ustar andriusandriusMol: C1=CC=CN1 Origin: a1 Options: Ring: atoms(a1 a2 a3 a4 a5); bonds(b1 b2 b3 b4 b5); aromatic(1) Chemistry-Ring-0.21/t/rings/byciclic_bond2.ring0000644000200400020040000000013414347026546021302 0ustar andriusandriusMol: C12CC1C2 Origin: b1 Options: all 1 Ring: atoms(a1 a2 a3); bonds(b1 b2 b3); aromatic(0) Chemistry-Ring-0.21/t/rings/benzene_kekule.ring0000644000200400020040000000015414347026546021425 0ustar andriusandriusMol: C1=CC=CC=C1 Origin: a1 Options: Ring: atoms(a1 a6 a5 a4 a3 a2); bonds(b6 b5 b4 b3 b2 b1); aromatic(1) Chemistry-Ring-0.21/t/rings/contained.ring0000644000200400020040000000013414347026546020401 0ustar andriusandriusMol: C12CC1C2 Origin: a2 Options: all 1 Ring: atoms(a2 a1 a3); bonds(b1 b3 b2); aromatic(0) Chemistry-Ring-0.21/t/rings/mirror.ring0000644000200400020040000000022614347026546017751 0ustar andriusandriusMol: C1CC1 Origin: a1 Options: mirror 1 all 1 Ring: atoms(a1 a2 a3); bonds(b1 b2 b3); aromatic(0) Ring: atoms(a1 a3 a2); bonds(b3 b2 b1); aromatic(0) Chemistry-Ring-0.21/t/pod.t0000644000200400020040000000044614347026546015407 0ustar andriusandriususe Test::More; my @files = (glob("lib/Chemistry/*.pm lib/Chemistry/Ring/*.pm")); my $n = @files; eval 'use Test::Pod'; if ($@) { plan skip_all => "You don't have Test::Pod installed"; } else { plan tests => $n; } for my $file (@files) { pod_file_ok($file, "POD for '$file'"); } Chemistry-Ring-0.21/Makefile.PL0000644000200400020040000000223714347026546016147 0ustar andriusandrius# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.012. use strict; use warnings; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Represent a ring as a substructure of a molecule", "AUTHOR" => "Ivan Tubert-Brohman ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Chemistry-Ring", "LICENSE" => "perl", "NAME" => "Chemistry::Ring", "PREREQ_PM" => { "Chemistry::Mol" => "0.24", "Statistics::Regression" => "0.15" }, "TEST_REQUIRES" => { "Chemistry::File::SMILES" => 0, "Test::Simple" => 0 }, "VERSION" => "0.21", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Chemistry::File::SMILES" => 0, "Chemistry::Mol" => "0.24", "Statistics::Regression" => "0.15", "Test::Simple" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); Chemistry-Ring-0.21/lib/0000775000200400020040000000000014347026546014741 5ustar andriusandriusChemistry-Ring-0.21/lib/Chemistry/0000775000200400020040000000000014347026546016710 5ustar andriusandriusChemistry-Ring-0.21/lib/Chemistry/Ring.pm0000644000200400020040000001401114347026546020140 0ustar andriusandriuspackage Chemistry::Ring; our $VERSION = '0.21'; # VERSION # $Id$ =head1 NAME Chemistry::Ring - Represent a ring as a substructure of a molecule =head1 SYNOPSIS use Chemistry::Ring; # already have a molecule in $mol... # create a ring with the first six atoms in $mol my $ring = Chemistry::Ring->new; $ring->add_atom($_) for $mol->atoms(1 .. 6); # find the centroid my $vector = $ring->centroid; # find the plane that fits the ring my ($normal, $distance) = $ring->plane; # is the ring aromatic? print "is aromatic!\n" if $ring->is_aromatic; # "aromatize" a molecule Chemistry::Ring::aromatize_mol($mol); # get the rings involving an atom (after aromatizing) my $rings = $mol->atoms(3)->attr('ring/rings'); =head1 DESCRIPTION This module provides some basic methods for representing a ring. A ring is a subclass of molecule, because it has atoms and bonds. Besides that, it has some useful geometric methods for finding the centroid and the ring plane, and methods for aromaticity detection. This module does not detect the rings by itself; for that, look at L. This module is part of the PerlMol project, L. =cut use strict; use warnings; use Math::VectorReal qw(:axis vector); use Statistics::Regression; use Chemistry::Mol; use base 'Chemistry::Mol', 'Exporter'; use Scalar::Util 'weaken'; our @EXPORT_OK = qw(aromatize_mol); our %EXPORT_TAGS = ( all => \@EXPORT_OK ); our $N = 0; our $DEBUG = 0; =head1 METHODS =over 4 =item Chemistry::Ring->new(name => value, ...) Create a new Ring object with the specified attributes. Same as C<< Chemistry::Mol->new >>. =cut sub nextID { "ring".++$N; } # make sure we don't become parent of the atoms added to us sub add_atom { shift->SUPER::add_atom_np(@_) } sub add_bond { shift->SUPER::add_bond_np(@_) } sub print { my $self = shift; return <{id} atoms: @{$self->{atoms}} bonds: @{$self->{bonds}} EOF } =item $ring->centroid Returns a vector with the centroid, defined as the average of the coordinates of all the atoms in the ring. The vecotr is a L object. =cut sub centroid { my $self = shift; my $c = O; # origin my $n = 0; for my $a ($self->atoms) { $c += $a->coords; ++$n; } $c = $c / $n; } =item my ($norm, $d) = $ring->plane Returns the normal and distance to the origin that define the plane that best fits the atoms in the ring, by using multivariate regression. The normal vector is a L object. =cut sub plane { my $self = shift; my $reg; if( $Statistics::Regression::VERSION < 0.52 ) { $reg = Statistics::Regression->new(3, "plane for $self", [qw(b mx my)]); } else { $reg = Statistics::Regression->new("plane for $self", [qw(b mx my)]); } for my $atom ($self->atoms) { my ($x, $y, $z) = $atom->coords->array; $reg->include($z, [1.0, $x, $y]); } $reg->print if $DEBUG; # convert the theta vector (z = a + bx + cy) to a normal vector and # distance to the origin my @coef = (@{$reg->theta}, -1.0); # -1 is d in a + bx + cx + dz = 0 my $d = shift @coef; # distance (not normalized) my $sum_sq = 0; # normalization constant $sum_sq += $_*$_ for @coef; $sum_sq ||= 1; ($d, @coef) = map { $_ / $sum_sq } ($d, @coef); # normalize return (vector(@coef)->norm, $d); } =item $ring->is_aromatic Naively guess whether ring is aromatic from the molecular graph, with a method based on Huckel's rule. This method is not very accurate, but works for simple molecules. Returns true or false. =cut sub is_aromatic { my ($self) = @_; my $n_pi = 0; for my $atom ($self->atoms) { no warnings 'uninitialized'; return 0 if ($atom->bonds + $atom->hydrogens > 3); # build bond order histogram my @order_freq = (0,0,0,0); for my $bond ($atom->bonds) { $order_freq[$bond->order]++; } return 0 if ($order_freq[3] or $order_freq[2] > 1); if ($order_freq[2] == 1) { $n_pi += 1; } elsif ($atom->symbol =~ /^[NOS]$/) { $n_pi += 2; } } #print "n_pi = $n_pi\n"; return ($n_pi % 4 == 2) ? 1 : 0; } 1; =back =head1 EXPORTABLE SUBROUTINES Nothing is exported by default, but you can export these subroutines explicitly, or all of them by using the ':all' tag. =over =item aromatize_mol($mol) Finds all the aromatic rings in the molecule and marks all the atoms and bonds in those rings as aromatic. It also adds the 'ring/rings' attribute to the molecule and to all ring atoms and bonds; this attribute is an array reference containing the list of rings that involve that atom or bond (or all the rings in the case of the molecule). NOTE (the ring/rings attribute is experimental and might change in future versions). =cut sub aromatize_mol { my ($mol) = @_; require Chemistry::Ring::Find; $_->aromatic(0) for ($mol->atoms, $mol->bonds); my @rings = Chemistry::Ring::Find::find_rings($mol); $mol->attr("ring/rings", \@rings); $_->attr("ring/rings", []) for ($mol->atoms, $mol->bonds); for my $ring (@rings) { if ($ring->is_aromatic) { $_->aromatic(1) for ($ring->atoms, $ring->bonds); } for ($ring->atoms, $ring->bonds) { my $ringlist = $_->attr("ring/rings") || []; push @$ringlist, $ring; weaken($ringlist->[-1]); $_->attr("ring/rings", $ringlist); } } @rings; } 1; =back =head1 SOURCE CODE REPOSITORY L =head1 SEE ALSO L, L, L, L. =head1 AUTHOR Ivan Tubert-Brohman =head1 COPYRIGHT Copyright (c) 2009 Ivan Tubert-Brohman. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut Chemistry-Ring-0.21/lib/Chemistry/Ring/0000775000200400020040000000000014347026546017607 5ustar andriusandriusChemistry-Ring-0.21/lib/Chemistry/Ring/Find.pm0000644000200400020040000002462414347026546021033 0ustar andriusandriuspackage Chemistry::Ring::Find; our $VERSION = '0.21'; # VERSION # $Id$ =head1 NAME Chemistry::Ring::Find - Find the rings (cycles) in a molecule =head1 SYNOPSIS use Chemistry::Ring::Find ':all'; # find the smallest ring containing $atom my $ring = find_ring($atom); # find all the rings containing $bond my @rings = find_ring($bond, all => 1); # see below for more options # find the six 4-atom rings in cubane @rings = find_rings($cubane); # find a cubane SSSR with five rings @rings = find_rings($cubane, sssr => 1); =head1 DESCRIPTION The Chemistry::Ring::Find module implements a breadth-first ring finding algorithm, and it can find all rings that contain a given atom or bond, the Smallest Set of Smallest Rings (SSSR), or the "almost SSSR", which is an unambiguous set of rings for cases such as cubane.The algorithms are based on ideas from: 1) Leach, A. R.; Dolata, D. P.; Prout, P. Automated Conformational Analysis and Structure Generation: Algorithms for Molecular Perception J. Chem. Inf. Comput. Sci. 1990, 30, 316-324 2) Figueras, J. Ring perception using breadth-first search. J. Chem. Inf. Comput. Sci. 1996, 36, 986-991. Ref. 2 is only used for find_ring, not for find_rings, because it has been shown that the overall SSSR method in ref 2 has bugs. Ref 1 inspired find_rings, which depends on find_ring. This module is part of the PerlMol project, L. =head1 FUNCTIONS These functions may be exported explicitly, or all by using the :all tag, but nothing is exported by default. =over =cut use strict; use warnings; no warnings qw(recursion); use Chemistry::Ring; our @ISA = qw(Exporter); our @EXPORT_OK = qw(find_ring find_rings); our %EXPORT_TAGS = ( all => \@EXPORT_OK ); our $DEBUG = 0; =item find_ring($origin, %opts) Find the smallest ring containing $origin, which may be either an atom or a bond. Returns a Chemistry::Ring object. Options: =over =item all If true, find all the rings containing $origin. If false, return the first ring found. Defaults to false. "All" is supposed to include only "simple" rings, that is, rings that are not a combination of smaller rings. =item min Only find rings with a the given minimum size. Defaults to zero. =item max Only find rings up to the given maximium size. Defaults to unlimited size. =item size Only find rings with this size. Same as setting min and max to the same size. Default: unspecified. =item exclude An array reference containing a list of atoms that must NOT be present in the ring. Defaults to the empty list. =item mirror If true, find each ring twice (forwards and backwards). Defaults to false. =back =cut # $origin is an atom # options: min, max, size, all, mirror, exclude sub find_ring { no warnings qw(uninitialized); my ($origin, %opts) = @_; my $min_size = $opts{min} || $opts{size} || 0; my $max_size = $opts{max} || $opts{size}; my %paths; my %bond_paths; my @q; my @rings; my %used_end_nodes; my $required_bond; my %exclude; @exclude{ @{$opts{exclude} || []} } = (); if ($origin->isa("Chemistry::Bond")) { $required_bond = $origin; ($origin) = $origin->atoms; } @q = ($origin); $paths{$origin} = [$origin]; $bond_paths{$origin} = []; # $path{$atom} means how to get to $atom from $origin my $a; while ($a = shift @q) { my $from = $paths{$a}[-2]; print "at $a from $from\n" if $DEBUG; for my $bn ($a->bonds_neighbors($from)) { my $nei = $bn->{to}; my $bond = $bn->{bond}; next if exists $exclude{$nei}; print " -> $nei\n" if $DEBUG; if ($paths{$nei}) { # a hypothetical check_collision() would have to do this: # %paths, $min_size, $max_size, $used_end_nodes # %bond_paths, $required_bond, @rings # check_size # check_redundant # check_required_bond # check_contains_ring # push print "found a path collision... " if $DEBUG; # check to make sure that the ring really started at $origin # and the size is what was requested my $size = @{$paths{$nei}} + @{$paths{$a}} - 1; #if($paths{$nei}[1] != $paths{$a}[1] if($paths{$nei}[1] ne $paths{$a}[1] and $size >= $min_size and !$max_size || $size <= $max_size) { print "VALID\n" if $DEBUG; my @atoms = (@{$paths{$a}}, reverse @{$paths{$nei}}); print "RING = ", print_path(\@atoms) if $DEBUG; pop @atoms; if ($used_end_nodes{$atoms[1]} and !$opts{mirror}) { print "skipping redundant ring\n" if $DEBUG; next; # don't want to find rings twice } my @bonds = (@{$bond_paths{$a}}, $bond, reverse @{$bond_paths{$nei}}); if ($required_bond and not grep {$_ eq $required_bond} @bonds) { print "does not include required bond (" . join(" ", $required_bond->atoms) . ")\n" if $DEBUG; next; } if (contains_ring(\@atoms, \@rings)) { print "contains another ring\n" if $DEBUG; next; } my $r = Chemistry::Ring->new; $r->add_atom(@atoms); $r->add_bond(@bonds); return $r unless $opts{all}; # FOUND VALID RING push @rings, $r; $used_end_nodes{$atoms[-1]} = 1; #@used_nodes{@atoms} = (); } else { print "NOT VALID", print_path( [@{$paths{$a}}, reverse @{$paths{$nei->id}}]) if $DEBUG; } } else { if (!$max_size || @{$paths{$a}} < ($max_size / 2) + 0.1) { push @q, $nei; print " pushing path\n" if $DEBUG; $paths{$nei} = [@{$paths{$a}}, $nei]; $bond_paths{$nei} = [@{$bond_paths{$a}}, $bond]; print print_path($paths{$nei}) if $DEBUG; } else { print "path too long; " if $DEBUG; print print_path($paths{$a}) if $DEBUG; #path too long } } } } @rings; } sub print_path { my $p = shift; my $ret = " PATH: "; for my $a (@$p) { $ret .= "$a - "; } $ret .= "\n"; } # contains_ring($atoms, $rings) # returns true if one of the rings in the array ref $rings is a proper subset # of the atom list in the array ref $atom. sub contains_ring { my ($atoms, $rings) = @_; my %seen; @seen{@$atoms} = (); for my $ring (@$rings) { my $unique_atoms = $ring->atoms; next if $unique_atoms >= @$atoms; # ring is same size or bigger # make sure that $ring has at least one atom not in $atoms for my $atom ($ring->atoms) { if (exists $seen{$atom}) { $unique_atoms--; } else { last; # there's at least one unique atom! } } return 1 unless $unique_atoms; } 0; } =item @rings = find_rings($mol, %options) Find "all" the rings in the molecule. In general it return the Smallest Set of Smallest Rings (SSSR). However, since it is well known that the SSSR is not unique for molecules such as cubane (where the SSSR consists of five unspecified four-member rings, even if the symmetry of the molecule would suggest that the six faces of the cube are equivalent), in such cases find_rings will return a non-ambiguous "non-smallest" set of smallest rings, unless the "sssr" option is given. For example, @rings = find_rings($cubane); # returns SIX four-member rings @rings = find_rings($cubane, sssr => 1); # returns FIVE four-member rings (an unspecified subset of # the six rings above.) =cut sub find_rings { my ($mol, %opts) = @_; my $visited = {}; my @ring_bonds; for my $atom ($mol->atoms) { next if $visited->{$atom}; push @ring_bonds, find_ring_bonds($mol, \%opts, $atom, $visited); } #print "rb($_; @{$_->{atoms}})\n" for @ring_bonds; my @rings; my $n_rings = @ring_bonds; #print "cyclomatic number=$n_rings\n"; for my $ring_bond (@ring_bonds) { push @rings, find_ring($ring_bond, all => 1) } my %seen; my @ring_keys = map { join " ", sort $_->atoms } @rings; #print "key($_)\n" for @ring_keys; @seen{@ring_keys} = @rings; @rings = sort { $a->atoms <=> $b->atoms } values %seen; if (!$opts{sssr} and @rings > $n_rings) { $n_rings++ if $rings[$n_rings]->atoms == $rings[$n_rings-1]->atoms; } splice @rings, $n_rings if defined $rings[$n_rings]; #splice @rings, $n_rings; @rings; } # find a set of "ring closure bonds" by doing a depth-first search # it will find a number of bonds equal to the cyclomatic number sub find_ring_bonds { my ($mol, $opts, $atom, $visited) = @_; my @ring_bonds; $visited->{$atom} = 1; for my $bn ($atom->bonds_neighbors) { my $nei = $bn->{to}; my $bond = $bn->{bond}; #next if $visited->{$bond}; next if not defined $bond or $visited->{$bond}; $visited->{$bond} = 1; if ($visited->{$nei}) { # closed ring #print "closing ring\n"; push @ring_bonds, $bond; } else { push @ring_bonds, find_ring_bonds($mol, $opts, $nei, $visited); } } @ring_bonds; } 1; =back =head1 BUGS The "all" option in find_ring doesn't quite work as expected. It finds all simple rings and some bridged rings. It never finds fused rings (which is good). =head1 SOURCE CODE REPOSITORY L =head1 SEE ALSO L =head1 AUTHOR Ivan Tubert-Brohman Eitub@cpan.orgE =head1 COPYRIGHT Copyright (c) 2009 Ivan Tubert-Brohman. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut Chemistry-Ring-0.21/META.json0000644000200400020040000000237314347026546015617 0ustar andriusandrius{ "abstract" : "Represent a ring as a substructure of a molecule", "author" : [ "Ivan Tubert-Brohman " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Chemistry-Ring", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Chemistry::Mol" : "0.24", "Statistics::Regression" : "0.15" } }, "test" : { "requires" : { "Chemistry::File::SMILES" : "0", "Test::Simple" : "0" } } }, "release_status" : "stable", "resources" : { "homepage" : "https://search.cpan.org/dist/Chemistry-Ring", "repository" : { "type" : "git", "url" : "git://github.com/perlmol/chemistry-ring.git", "web" : "https://github.com/perlmol/chemistry-ring" } }, "version" : "0.21", "x_generated_by_perl" : "v5.30.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.19" } Chemistry-Ring-0.21/MANIFEST.SKIP0000644000200400020040000000013414347026546016065 0ustar andriusandrius\.mol$ \.gz$ \.pl$ \.BAK$ \.bak$ TODO swp$ ^.$ ^Makefile$ Makefile.old ^blib pm_to_blib CVS