Graph-VF2-0.2.0/0000775000200400020040000000000015141125241013021 5ustar andriusandriusGraph-VF2-0.2.0/dist.ini0000644000200400020040000000116515141125241014466 0ustar andriusandriusname = Graph-VF2 author = Andrius Merkys license = BSD copyright_holder = Andrius Merkys copyright_year = 2025-2026 version = 0.2.0 [@Filter] -bundle = @Basic -remove = License -remove = MakeMaker [AutoMetaResources] homepage = http://search.cpan.org/dist/ repository.github = user:merkys bugtracker.github = user:merkys [MakeMaker::Awesome] WriteMakefile_arg = CC => 'g++', LIBS => [ '-lstdc++' ] [MetaJSON] [MinimumPerlFast] [OurPkgVersion] [Prereqs / Runtime] -phase = runtime Graph::Undirected = 0 [Prereqs / Test] -phase = test Chemistry::OpenSMILES = 0 Graph::Undirected = 0 Test::More = 0 Graph-VF2-0.2.0/typemap0000644000200400020040000000000015141125241014407 0ustar andriusandriusGraph-VF2-0.2.0/LICENSE0000644000200400020040000000270715141125241014032 0ustar andriusandriusCopyright (c) 2025-2026 Andrius Merkys All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Graph-VF2-0.2.0/Changes0000644000200400020040000000015115141125241014307 0ustar andriusandrius0.2.0 2026-02-05 - Implement support for edge comparison. 0.1.0 2025-11-28 - Initial release. Graph-VF2-0.2.0/README0000644000200400020040000000051415141125241013677 0ustar andriusandriusThis archive contains the distribution Graph-VF2, version 0.2.0: VF2 subgraph isomorphism detection method for Perl Graph This software is Copyright (c) 2025-2026 by Andrius Merkys. This is free software, licensed under: The (three-clause) BSD License This README file was generated by Dist::Zilla::Plugin::Readme v6.012. Graph-VF2-0.2.0/MANIFEST0000644000200400020040000000036615141125241014155 0ustar andriusandrius# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012. Changes LICENSE MANIFEST META.json META.yml Makefile.PL README VF2.xs dist.ini lib/Graph/VF2.pm t/01_simple.t t/02_vf2_sub_graph_iso_example.t t/03_SMILES.t typemap Graph-VF2-0.2.0/META.yml0000644000200400020040000000141615141125241014272 0ustar andriusandrius--- abstract: 'VF2 subgraph isomorphism detection method for Perl Graph' author: - 'Andrius Merkys ' build_requires: Chemistry::OpenSMILES: '0' Graph::Undirected: '0' Test::More: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010' license: bsd meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Graph-VF2 requires: Graph::Undirected: '0' perl: '5.008' resources: bugtracker: https://github.com/merkys/graph-vf2/issues homepage: http://search.cpan.org/dist/ repository: git://github.com/merkys/graph-vf2.git version: 0.2.0 x_generated_by_perl: v5.30.0 x_serialization_backend: 'YAML::Tiny version 1.73' Graph-VF2-0.2.0/VF2.xs0000644000200400020040000001334315141125241013774 0ustar andriusandrius#include #include using namespace boost; #define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" // Handle equivalence template struct equivalence { equivalence(const PropertyMapFirst property_map1, const PropertyMapSecond property_map2): m_property_map1(property_map1), m_property_map2(property_map2) {} template bool operator()(const ItemFirst item1, const ItemSecond item2) { return get(m_property_map2, item2)[get(m_property_map1, item1)]; } private: const PropertyMapFirst m_property_map1; const PropertyMapSecond m_property_map2; }; template equivalence make_equivalence(const PropertyMapFirst property_map1, const PropertyMapSecond property_map2) { return (equivalence (property_map1, property_map2)); } template struct correspondence_callback { correspondence_callback(const Graph1& graph1, const Graph2& graph2, std::vector& correspondence) : graph1_(graph1), graph2_(graph2), correspondence_(correspondence) {} template bool operator()(CorrespondenceMap1To2 f, CorrespondenceMap2To1) const { BGL_FORALL_VERTICES_T(v, graph1_, Graph1) { correspondence_.push_back( get(vertex_index_t(), graph1_, v) ); correspondence_.push_back( get(vertex_index_t(), graph2_, get(f, v)) ); } return true; } private: const Graph1& graph1_; const Graph2& graph2_; std::vector& correspondence_; }; MODULE = Graph::VF2 PACKAGE = Graph::VF2 SV * _vf2(vertices1, edges1, vertices2, edges2, vertex_map, edge_map) SV * vertices1 SV * edges1 SV * vertices2 SV * edges2 SV * vertex_map SV * edge_map CODE: typedef property< vertex_name_t, ssize_t> query_vertex_property; typedef property< vertex_name_t, bool*> target_vertex_property; typedef property< edge_name_t, ssize_t> query_edge_property; typedef property< edge_name_t, bool*> target_edge_property; typedef adjacency_list< setS, vecS, undirectedS, query_vertex_property, query_edge_property > query_graph; typedef adjacency_list< setS, vecS, undirectedS, target_vertex_property, target_edge_property > target_graph; // Build graph1 query_graph graph1; int num_vertices1 = av_top_index((AV*) SvRV(vertices1)) + 1; int num_edges1 = av_top_index((AV*) SvRV(edges1)) + 1; for (ssize_t i = 0; i < num_vertices1; i++) add_vertex( query_vertex_property(i), graph1 ); for (ssize_t i = 0; i < num_edges1; i++) { AV * edge = (AV*) SvRV( av_fetch( (AV*) SvRV(edges1), i, 0 )[0] ); add_edge( SvIV( av_fetch( edge, 0, 0 )[0] ), SvIV( av_fetch( edge, 1, 0 )[0] ), query_edge_property(i), graph1 ); } // Build graph2 target_graph graph2; int num_vertices2 = av_top_index((AV*) SvRV(vertices2)) + 1; int num_edges2 = av_top_index((AV*) SvRV(edges2)) + 1; for (ssize_t i = 0; i < num_vertices2; i++) { AV * line = (AV*) SvRV( av_fetch( (AV*) SvRV(vertex_map), i, 0 )[0] ); bool* vector = (bool*)calloc(num_vertices1, sizeof(bool)); for (ssize_t j = 0; j < num_vertices1; j++) { vector[j] = SvIV( av_fetch( line, j, 0 )[0] ); } add_vertex( target_vertex_property(vector), graph2 ); } for (ssize_t i = 0; i < num_edges2; i++) { AV * edge = (AV*) SvRV( av_fetch( (AV*) SvRV(edges2), i, 0 )[0] ); AV * line = (AV*) SvRV( av_fetch( (AV*) SvRV(edge_map), i, 0 )[0] ); bool* vector = (bool*)calloc(num_edges1, sizeof(bool)); for (ssize_t j = 0; j < num_edges1; j++) vector[j] = SvIV( av_fetch( line, j, 0 )[0] ); add_edge( SvIV( av_fetch( edge, 0, 0 )[0] ), SvIV( av_fetch( edge, 1, 0 )[0] ), target_edge_property(vector), graph2 ); } auto vertex_comp = make_equivalence(get(vertex_name, graph1), get(vertex_name, graph2)); auto edge_comp = make_equivalence(get(edge_name, graph1), get(edge_name, graph2)); std::vector correspondence; // Create callback to print mappings correspondence_callback< query_graph, target_graph > callback(graph1, graph2, correspondence); // Get all subgraph isomorphism mappings between graph1 and graph2. // Edges are assumed to be always equivalent. vf2_subgraph_iso(graph1, graph2, callback, get(vertex_index, graph1), get(vertex_index, graph2), vertex_order_by_mult(graph1), edge_comp, vertex_comp); // Free the allocated memory auto vertex_property_map = get(vertex_name, graph2); auto edge_property_map = get(edge_name, graph2); BGL_FORALL_VERTICES_T(vertex, graph2, target_graph) free(vertex_property_map[vertex]); BGL_FORALL_EDGES_T(edge, graph2, target_graph) free(edge_property_map[edge]); AV* map = newAV(); for (int n : correspondence) av_push( map, newSViv( n ) ); RETVAL = newRV_noinc( (SV*)map ); OUTPUT: RETVAL Graph-VF2-0.2.0/t/0000775000200400020040000000000015141125241013264 5ustar andriusandriusGraph-VF2-0.2.0/t/01_simple.t0000644000200400020040000000052715141125241015244 0ustar andriusandriususe strict; use warnings; use Graph::Undirected; use Graph::VF2 qw( matches ); use Test::More tests => 2; my $g1 = Graph::Undirected->new; my $g2 = Graph::Undirected->new; $g1->add_cycle( 1..3 ); $g2->add_cycle( 1..10 ); $g2->add_cycle( 1..3 ); is scalar matches( $g1, $g2 ), 6; $g2->add_cycle( 6..8 ); is scalar matches( $g1, $g2 ), 12; Graph-VF2-0.2.0/t/02_vf2_sub_graph_iso_example.t0000644000200400020040000000134515141125241021067 0ustar andriusandriususe strict; use warnings; use Graph::Undirected; use Graph::VF2 qw( matches ); use Test::More tests => 2; my $g1 = Graph::Undirected->new; $g1->add_edge(0, 6); $g1->add_edge(0, 7); $g1->add_edge(1, 5); $g1->add_edge(1, 7); $g1->add_edge(2, 4); $g1->add_edge(2, 5); $g1->add_edge(2, 6); $g1->add_edge(3, 4); my $g2 = Graph::Undirected->new; $g2->add_edge(0, 6); $g2->add_edge(0, 8); $g2->add_edge(1, 5); $g2->add_edge(1, 7); $g2->add_edge(2, 4); $g2->add_edge(2, 7); $g2->add_edge(2, 8); $g2->add_edge(3, 4); $g2->add_edge(3, 5); $g2->add_edge(3, 6); is scalar matches( $g1, $g2 ), 8; my $g3 = Graph::Undirected->new; for my $edge ($g2->edges) { $g3->add_edge(8 - $edge->[0], 8 - $edge->[1]); } is scalar matches( $g1, $g3 ), 8; Graph-VF2-0.2.0/t/03_SMILES.t0000644000200400020040000000452015141125241014746 0ustar andriusandriususe strict; use warnings; use Chemistry::OpenSMILES qw( %bond_symbol_to_order ); use Chemistry::OpenSMILES::Parser; use Graph::VF2 qw( matches ); use Test::More tests => 8; my $parser = Chemistry::OpenSMILES::Parser->new; my( $phenanthroline ) = $parser->parse( 'c1cc2ccc3cccnc3c2nc1' ); my( $phenanthrene ) = $parser->parse( 'C1=CC=C2C(=C1)C=CC3=CC=CC=C32' ); my( $benzene ) = $parser->parse( 'c1ccccc1' ); # Drop H atoms, otherwise there will be no matches $benzene->delete_vertices( grep { $_->{symbol} eq 'H' } $benzene->vertices ); my $vertex_correspondence_sub; my $edge_correspondence_sub; $vertex_correspondence_sub = sub { ucfirst $_[0]->{symbol} eq ucfirst $_[1]->{symbol} }; is scalar matches( $benzene, $phenanthroline, { vertex_correspondence_sub => $vertex_correspondence_sub } ), 12; is scalar matches( $benzene, $phenanthrene, { vertex_correspondence_sub => $vertex_correspondence_sub } ), 36; # Require strict match of the chemical symbol $vertex_correspondence_sub = sub { $_[0]->{symbol} eq $_[1]->{symbol} }; is scalar matches( $benzene, $phenanthroline, { vertex_correspondence_sub => $vertex_correspondence_sub } ), 12; is scalar matches( $benzene, $phenanthrene, { vertex_correspondence_sub => $vertex_correspondence_sub } ), 0; # Select all six-membered cycles is scalar matches( $benzene, $phenanthroline ), 36; is scalar matches( $benzene, $phenanthrene ), 36; my $any_vertex = Graph::Undirected->new; $any_vertex->add_vertex( 0 ); $vertex_correspondence_sub = sub { $_[1]->{symbol} =~ /^[CN]$/i }; is scalar matches( $any_vertex, $phenanthroline, { vertex_correspondence_sub => $vertex_correspondence_sub } ), 14; sub bond_order { my $graph = shift; my @edge = @_; return 1 unless $graph->has_edge_attribute( @edge, 'bond' ); return $bond_symbol_to_order{$graph->get_edge_attribute( @edge, 'bond' )}; } ( $benzene ) = $parser->parse( 'C1=CC=CC=C1' ); $benzene->delete_vertices( grep { $_->{symbol} eq 'H' } $benzene->vertices ); $vertex_correspondence_sub = sub { $_[0]->{symbol} eq $_[1]->{symbol} }; $edge_correspondence_sub = sub { bond_order( $benzene, @_[0..1] ) == bond_order( $phenanthrene, @_[2..3] ) }; is scalar matches( $benzene, $phenanthrene, { vertex_correspondence_sub => $vertex_correspondence_sub, edge_correspondence_sub => $edge_correspondence_sub } ), 12; Graph-VF2-0.2.0/Makefile.PL0000644000200400020040000000241415141125241014772 0ustar andriusandrius# This Makefile.PL for Graph-VF2 was generated by # Dist::Zilla::Plugin::MakeMaker::Awesome 0.48. # Don't edit it but the dist.ini and plugins used to construct it. use strict; use warnings; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "VF2 subgraph isomorphism detection method for Perl Graph", "AUTHOR" => "Andrius Merkys ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Graph-VF2", "LICENSE" => "bsd", "NAME" => "Graph::VF2", "PREREQ_PM" => { "Graph::Undirected" => 0 }, "TEST_REQUIRES" => { "Chemistry::OpenSMILES" => 0, "Graph::Undirected" => 0, "Test::More" => 0 }, "VERSION" => "0.2.0", "test" => { "TESTS" => "t/*.t" } ); %WriteMakefileArgs = ( %WriteMakefileArgs, CC => 'g++', LIBS => [ '-lstdc++' ], ); my %FallbackPrereqs = ( "Chemistry::OpenSMILES" => 0, "Graph::Undirected" => 0, "Test::More" => 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); Graph-VF2-0.2.0/lib/0000775000200400020040000000000015141125241013567 5ustar andriusandriusGraph-VF2-0.2.0/lib/Graph/0000775000200400020040000000000015141125241014630 5ustar andriusandriusGraph-VF2-0.2.0/lib/Graph/VF2.pm0000644000200400020040000000772715141125241015576 0ustar andriusandriuspackage Graph::VF2; # ABSTRACT: VF2 subgraph isomorphism detection method for Perl Graph our $VERSION = '0.2.0'; # VERSION =head1 NAME Graph::MoreUtils - VF2 subgraph isomorphism detection method for Perl Graph =head1 SYNOPSIS use Graph::Undirected; use Graph::VF2 qw( matches ); my $small = Graph::Undirected->new; my $large = Graph::Undirected->new; # Create graphs here # Find all subgraphs of $small in $large: my @matches = matches( $small, $large ); =cut use strict; use warnings; use Graph::Undirected; require Exporter; our @ISA = qw( Exporter ); our @EXPORT_OK = qw( matches ); require XSLoader; XSLoader::load('Graph::VF2', $VERSION); =head1 METHODS =head2 C Takes two L objects, C<$g1> and C<$g2> and returns an array of occurrences of C<$g1> in C<$g2>. Returned array consists of array references, each array reference describing one occurrence. In it, encoded as array references, is the list of pairwise vertex correspondences. First item in a pair is a vertex from C<$g1>, and second item being a vertex in C<$g2>. No attempt is made to collate isomorphic matches. Thus a search of N-element cycle graph in itself will produce 2 * N matches due to graph's symmetry. C<$options> is a hash reference of options with the following keys: =over =item C A subroutine reference used to evaluate the equality of vertices, called with C<$v1> and C<$v2> from C<$g1> and C<$g2>, accordingly. Should return Perl true and false equivalents to signify match and non-match, accordingly. Unless provided, all vertices are treated as equal. =item C A subroutine reference used to evaluate the equality of edges, called with C<$v1> and C<$v2> from C<$g1>, and C<$v3> and C<$v4> from C<$g2>, accordingly. Edges are represented as pairs of vertices and are passed as a flat array. Should return Perl true and false equivalents to signify match and non-match, accordingly. Unless provided, all edges are treated as equal. =back =cut sub matches { my( $g1, $g2, $options ) = @_; die 'input graphs must be undirected' unless $g1->isa( Graph::Undirected:: ) && $g2->isa( Graph::Undirected:: ); $options = {} unless $options; my $vertex_correspondence_sub = exists $options->{vertex_correspondence_sub} ? $options->{vertex_correspondence_sub} : sub { 1 }; my $edge_correspondence_sub = exists $options->{edge_correspondence_sub} ? $options->{edge_correspondence_sub} : sub { 1 }; my @vertices1 = $g1->vertices; my %vertices1 = map { $vertices1[$_] => $_ } 0..$#vertices1; my @edges1 = map { [ $vertices1{$_->[0]}, $vertices1{$_->[1]} ] } $g1->edges; my @vertices2 = $g2->vertices; my %vertices2 = map { $vertices2[$_] => $_ } 0..$#vertices2; my @edges2 = map { [ $vertices2{$_->[0]}, $vertices2{$_->[1]} ] } $g2->edges; my $vertex_map = []; for my $vertex (@vertices2) { push @$vertex_map, [ map { int $vertex_correspondence_sub->($_, $vertex) } @vertices1 ]; } my $edge_map = []; for my $edge (@edges2) { push @$edge_map, [ map { int $edge_correspondence_sub->( ( map { $vertices1[$_] } @$_ ), ( map { $vertices2[$_] } @$edge) ) } @edges1 ]; } my $correspondence = _vf2( \@vertices1, \@edges1, \@vertices2, \@edges2, $vertex_map, $edge_map ); my @matches; while (my @match = splice @$correspondence, 0, 2 * @vertices1) { push @matches, [ map { [ $vertices1[$match[2 * $_]], $vertices2[$match[2 * $_ + 1]] ] } 0..$#vertices1 ]; } return @matches; } =head1 AUTHORS Andrius Merkys, Emerkys@cpan.orgE =cut 1; Graph-VF2-0.2.0/META.json0000644000200400020040000000251615141125241014444 0ustar andriusandrius{ "abstract" : "VF2 subgraph isomorphism detection method for Perl Graph", "author" : [ "Andrius Merkys " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "bsd" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Graph-VF2", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Graph::Undirected" : "0", "perl" : "5.008" } }, "test" : { "requires" : { "Chemistry::OpenSMILES" : "0", "Graph::Undirected" : "0", "Test::More" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/merkys/graph-vf2/issues" }, "homepage" : "http://search.cpan.org/dist/", "repository" : { "type" : "git", "url" : "git://github.com/merkys/graph-vf2.git", "web" : "https://github.com/merkys/graph-vf2" } }, "version" : "0.2.0", "x_generated_by_perl" : "v5.30.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.19" }