Skip to content

Commit

Permalink
Finish 3.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
gkellogg committed Feb 2, 2021
2 parents 8ddb468 + c9c0edf commit 63ae7c4
Show file tree
Hide file tree
Showing 28 changed files with 195 additions and 64 deletions.
1 change: 1 addition & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
repo_token: 3Odyub10Nw95YJypsDygaJlMisXW0LruT
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This workflow runs continuous CI across different versions of ruby on all branches and pull requests to develop.

name: CI
on:
push:
branches: [ '**' ]
pull_request:
branches: [ develop ]
workflow_dispatch:

jobs:
tests:
name: Ruby ${{ matrix.ruby }}
if: "contains(github.event.commits[0].message, '[ci skip]') == false"
runs-on: ubuntu-latest
env:
CI: true
strategy:
fail-fast: false
matrix:
ruby:
- 2.4
- 2.5
- 2.6
- 2.7
- 3.0
- ruby-head
- jruby
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install dependencies
run: bundle install --jobs 4 --retry 3
- name: Run tests
run: bundle exec rspec spec

2 changes: 1 addition & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
* Ben Lavender <blavender@gmail.com> (Lead developer)
* Arto Bendiken <arto.bendiken@gmail.com>
* Gregg Kellogg <gregg@kellogg-assoc.com>
* Gregg Kellogg <gregg@greggkellogg.net>
14 changes: 8 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Community contributions are essential for keeping Ruby RDF great. We want to kee

This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration.

* create or respond to an issue on the [Github Repository](http://github.com/ruby-rdf/rdf-isomorphic/issues)
* create or respond to an issue on the [Github Repository](https://github.com/ruby-rdf/rdf-isomorphic/issues)
* Fork and clone the repo:
`git clone git@github.com:your-username/rdf-isomorphic.git`
* Install bundle:
Expand All @@ -28,9 +28,11 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage devel
enough, be assured we will eventually add you in there.
* Do note that in order for us to merge any non-trivial changes (as a rule
of thumb, additions larger than about 15 lines of code), we need an
explicit [public domain dedication][PDD] on record from you.
explicit [public domain dedication][PDD] on record from you,
which you will be asked to agree to on the first commit to a repo within the organization.
Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization.

[YARD]: http://yardoc.org/
[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
[pr]: https://github.com/ruby-rdf/rdf-isomorphic/compare/
[YARD]: https://yardoc.org/
[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
[PDD]: https://unlicense.org/#unlicensing-contributions
[pr]: https://github.com/ruby-rdf/rdf/compare/
39 changes: 21 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
This is an [RDF.rb][] extension for RDF Isomorphism functionality for RDF::Enumerables.
That includes RDF::Repository, RDF::Graph, query results, and more.

For more information about [RDF.rb][], see <http://www.rubydoc.info/github/ruby-rdf/rdf/>
For more information about [RDF.rb][], see <https://www.rubydoc.info/github/ruby-rdf/rdf/>

[![Gem Version](https://badge.fury.io/rb/rdf-isomorphic.png)](http://badge.fury.io/rb/rdf-isomorphic)
[![Build Status](https://travis-ci.org/ruby-rdf/rdf-isomorphic.png)](https://travis-ci.org/ruby-rdf/rdf-isomorphic)
[![Gem Version](https://badge.fury.io/rb/rdf-isomorphic.png)](https://badge.fury.io/rb/rdf-isomorphic)
[![Build Status](https://github.com/ruby-rdf/rdf-isomorphic/workflows/CI/badge.svg?branch=develop)](https://github.com/ruby-rdf/rdf-isomorphic/actions?query=workflow%3ACI)
[![Coverage Status](https://coveralls.io/repos/ruby-rdf/rdf-isomorphic/badge.svg?branch=develop)](https://coveralls.io/github/ruby-rdf/rdf-isomorphic?branch=develop)
[![Gitter chat](https://badges.gitter.im/ruby-rdf/rdf.png)](https://gitter.im/ruby-rdf/rdf)

## Synopsis:

require 'rdf/isomorphic'
require 'rdf/ntriples'


a = RDF::Repository.load './tests/isomorphic/test1/test1-1.nt'
a.first
# < RDF::Statement:0xd344c4(<http://example.org/a> <http://example.org/prop> <_:abc> .) >
Expand All @@ -32,8 +33,7 @@ For more information about [RDF.rb][], see <http://www.rubydoc.info/github/ruby-
## Algorithm

The algorithm used here is very similar to the one described by Jeremy Carroll
in <http://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf>. See
<http://blog.datagraph.org/2010/03/rdf-isomorphism>.
in <https://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf>.

Generally speaking, the Carroll algorithm is a very good fit for RDF graphs. It
is a specialization of the naive factorial-time test for graph isomorphism,
Expand Down Expand Up @@ -64,14 +64,15 @@ specs in RDF libraries. Try this in your tests:
end

### Information
* Author: Ben Lavender <blavender@gmail.com> - <http://bhuga.net>
* Author: Arto Bendiken <arto.bendiken@gmail.com> - <http://ar.to>
* Source: <http://github.com/ruby-rdf/rdf-isomorphic>
* Issues: <http://github.com/ruby-rdf/rdf-isomorphic/issues>
* Author: Ben Lavender <blavender@gmail.com> - <https://bhuga.net/>
* Author: Arto Bendiken <arto.bendiken@gmail.com> - <https://ar.to/>
* Author: Gregg Kellogg <gregg@greggkellogg.net> - <https://greggkellogg.net/>
* Source: <https://github.com/ruby-rdf/rdf-isomorphic>
* Issues: <https://github.com/ruby-rdf/rdf-isomorphic/issues>

### See also
* RDF.rb: <http://ruby-rdf.github.com>
* RDF.rb source: <http://github.com/ruby-rdf/rdf>
* RDF.rb: <https://ruby-rdf.github.com>
* RDF.rb source: <https://github.com/ruby-rdf/rdf>

## Contributing

Expand All @@ -90,14 +91,16 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
enough, be assured we will eventually add you in there.
* Do note that in order for us to merge any non-trivial changes (as a rule
of thumb, additions larger than about 15 lines of code), we need an
explicit [public domain dedication][PDD] on record from you.
explicit [public domain dedication][PDD] on record from you,
which you will be asked to agree to on the first commit to a repo within the organization.
Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization.

## License

This is free and unencumbered public domain software. For more information,
see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.

[RDF.rb]: http://ruby-rdf.github.com/
[YARD]: http://yardoc.org/
[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
[RDF.rb]: https://ruby-rdf.github.com/
[YARD]: https://yardoc.org/
[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
[PDD]: https://unlicense.org/#unlicensing-contributions
2 changes: 1 addition & 1 deletion UNLICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>
For more information, please refer to <https://unlicense.org/>
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.1.0
3.1.1
46 changes: 46 additions & 0 deletions etc/doap.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@base <https://rubygems.org/gems/rdf-isomorphic> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dc: <http://purl.org/dc/terms/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix doap: <http://usefulinc.com/ns/doap#> .

<> a doap:Project ;
doap:name "RDF::Isomorphic" ;
doap:homepage <> ;
doap:license <https://unlicense.org/1.0/> ;
doap:shortdesc "RDF Graph/Dataset Isomorphism as defined in RDF 1.1 Concepts for Ruby."@en ;
doap:description "RDF.rb extension for graph bijections and isomorphic equivalence."@en ;
doap:created "2010-02-01" ;
doap:programming-language "Ruby" ;
doap:category <http://dbpedia.org/resource/Resource_Description_Framework>,
<http://dbpedia.org/resource/Ruby_(programming_language)> ;
doap:implements <http://www.w3.org/TR/rdf11-concepts/> ;
doap:download-page <> ;
doap:bug-database <https://github.com/ruby-rdf/rdf-isomorphic/issues> ;
doap:blog <https://ar.to/>, <https://greggkellogg.net/> ;
doap:developer <https://ar.to/#self>, <https://bhuga.net/#ben>, <https://greggkellogg.net/foaf#me> ;
doap:maintainer <https://greggkellogg.net/foaf#me> ;
doap:documenter <https://ar.to/#self>, <https://bhuga.net/#ben>, <https://greggkellogg.net/foaf#me> ;
foaf:maker <https://bhuga.net/#ben> ;
dc:creator <https://bhuga.net/#ben> .

<https://bhuga.net/#ben> a foaf:Person ;
foaf:name "Ben Lavender" ;
foaf:mbox <mailto:blavender@gmail.com> ;
foaf:mbox_sha1sum "dbf45f4ffbd27b67aa84f02a6a31c144727d10af" ;
foaf:homepage <https://bhuga.net/> ;
foaf:made <> .

<https://ar.to/#self> a foaf:Person ;
foaf:name "Arto Bendiken" ;
foaf:mbox <mailto:arto@bendiken.net> ;
foaf:mbox_sha1sum "a033f652c84a4d73b8c26d318c2395699dd2bdfb" ;
foaf:homepage <https://ar.to/> .

<https://greggkellogg.net/foaf#me> a foaf:Person ;
foaf:name "Gregg Kellogg" ;
foaf:mbox <mailto:gregg@greggkellogg.net> ;
foaf:mbox_sha1sum "35bc44e6d0070e5ad50ccbe0d24403c96af2b9bd" ;
foaf:homepage <https://greggkellogg.net/>;
rdfs:isDefinedBy <https://greggkellogg.net/foaf> .
70 changes: 40 additions & 30 deletions lib/rdf/isomorphic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,22 @@ module RDF
#
# RDF::Isomorphic provides the functions isomorphic_with and bijection_to for RDF::Enumerable.
#
# @see http://www.rubydoc.info/github/ruby-rdf/rdf/
# @see http://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf
# @see https://www.rubydoc.info/github/ruby-rdf/rdf/
# @see https://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf
module Isomorphic
autoload :VERSION, 'rdf/isomorphic/version'

# Returns `true` if this RDF::Enumerable is isomorphic with another.
#
# Takes a canonicalize: true argument. If true, RDF::Literals will be
# canonicalized while producing a bijection. This results in broader
# matches for isomorphism in the case of equivalent literals with different
# representations.
#
# @param opts [Hash<Symbol => Any>] options
# @param canonicalize [Boolean] (false)
# If `true`, RDF::Literals will be canonicalized while producing a bijection. This results in broader matches for isomorphism in the case of equivalent literals with different representations.
# @param opts [Hash<Symbol => Any>] other options ignored
# @param other [RDF::Enumerable]
# @return [Boolean]
# @example
# repository_a.isomorphic_with repository_b #=> true
def isomorphic_with?(other, **opts)
!(bijection_to(other, **opts).nil?)
def isomorphic_with?(other, canonicalize: false, **opts)
!(bijection_to(other, canonicalize: false, **opts).nil?)
end

alias_method :isomorphic?, :isomorphic_with?
Expand All @@ -45,9 +42,11 @@ def isomorphic_with?(other, **opts)
# @example
# repository_a.bijection_to repository_b
# @param other [RDF::Enumerable]
# @param opts [Hash<Symbol => Any>] options
# @param canonicalize [Boolean] (false)
# If true, RDF::Literals will be canonicalized while producing a bijection. This results in broader matches for isomorphism in the case of equivalent literals with different representations.
# @param opts [Hash<Symbol => Any>] other options ignored
# @return [Hash, nil]
def bijection_to(other, **opts)
def bijection_to(other, canonicalize: false, **opts)

grounded_stmts_match = (count == other.count)

Expand All @@ -65,7 +64,10 @@ def bijection_to(other, **opts)

nodes = RDF::Isomorphic.blank_nodes_in(blank_stmts)
other_nodes = RDF::Isomorphic.blank_nodes_in(other_blank_stmts)
build_bijection_to blank_stmts, nodes, other_blank_stmts, other_nodes, {}, {}, **opts
build_bijection_to blank_stmts, nodes, other_blank_stmts, other_nodes,
these_grounded_hashes: {},
other_grounded_hashes: {},
canonicalize: false
else
nil
end
Expand All @@ -77,7 +79,7 @@ def bijection_to(other, **opts)
# The main recursive bijection algorithm.
#
# This algorithm is very similar to the one explained by Jeremy Carroll in
# http://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf. Page 12 has the
# https://www.hpl.hp.com/techreports/2001/HPL-2001-293.pdf. Page 12 has the
# relevant pseudocode.
#
# Many more comments are in the method itself.
Expand All @@ -88,19 +90,23 @@ def bijection_to(other, **opts)
# @param [Array] other_nodes
# @param [Hash] these_grounded_hashes
# @param [Hash] other_grounded_hashes
# @param [Hash] opts
# @param canonicalize [Boolean] (false)
# If true, RDF::Literals will be canonicalized while producing a bijection. This results in broader matches for isomorphism in the case of equivalent literals with different representations.
# @return [nil,Hash]
# @private
def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_grounded_hashes = {}, other_grounded_hashes = {}, **opts)
def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes,
these_grounded_hashes: {},
other_grounded_hashes: {},
canonicalize: false)

# Create a hash signature of every node, based on the signature of
# statements it exists in.
# We also save hashes of nodes that cannot be reliably known; we will use
# that information to eliminate possible recursion combinations.
#
# Any mappings given in the method parameters are considered grounded.
these_hashes, these_ungrounded_hashes = RDF::Isomorphic.hash_nodes(anon_stmts, nodes, these_grounded_hashes, opts[:canonicalize])
other_hashes, other_ungrounded_hashes = RDF::Isomorphic.hash_nodes(other_anon_stmts, other_nodes, other_grounded_hashes, opts[:canonicalize])
these_hashes, these_ungrounded_hashes = RDF::Isomorphic.hash_nodes(anon_stmts, nodes, these_grounded_hashes, canonicalize: canonicalize)
other_hashes, other_ungrounded_hashes = RDF::Isomorphic.hash_nodes(other_anon_stmts, other_nodes, other_grounded_hashes, canonicalize: canonicalize)

# Grounded hashes are built at the same rate between the two graphs (if
# they are isomorphic). If there exists a grounded node in one that is
Expand All @@ -116,7 +122,7 @@ def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_g
# around for when we recurse later (we only recurse on ungrounded nodes)
bijection = {}
nodes.each do | node |
other_node, other_hash = other_ungrounded_hashes.find do | other_node, other_hash |
other_node, _ = other_ungrounded_hashes.find do | other_node, other_hash |
# we need to use eql?, as coincedentally-named bnode identifiers are == in rdf.rb
these_ungrounded_hashes[node].eql? other_hash
end
Expand Down Expand Up @@ -148,7 +154,11 @@ def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_g
next unless these_ungrounded_hashes[node] == other_ungrounded_hashes[other_node]

hash = Digest::SHA1.hexdigest(node.to_s)
bijection = build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_hashes.merge( node => hash), other_hashes.merge(other_node => hash))
bijection = build_bijection_to(anon_stmts, nodes,
other_anon_stmts, other_nodes,
these_grounded_hashes: these_hashes.merge( node => hash),
other_grounded_hashes: other_hashes.merge(other_node => hash),
canonicalize: canonicalize)
end
bijection
end
Expand All @@ -162,7 +172,7 @@ def build_bijection_to(anon_stmts, nodes, other_anon_stmts, other_nodes, these_g
# @param [Array<RDF::Statement>] blank_stmt_list
# @return [Array<RDF::Node>]
def self.blank_nodes_in(blank_stmt_list)
blank_stmt_list.map {|statement | statement.to_quad.compact.select(&:node?)}.flatten.uniq
blank_stmt_list.map {|statement | statement.terms.select(&:node?)}.flatten.uniq
end

# Given a set of statements, create a mapping of node => SHA1 for a given
Expand All @@ -180,7 +190,7 @@ def self.blank_nodes_in(blank_stmt_list)
# @param [Hash] grounded_hashes
# @private
# @return [Hash, Hash]
def self.hash_nodes(statements, nodes, grounded_hashes, canonicalize = false)
def self.hash_nodes(statements, nodes, grounded_hashes, canonicalize: false)
hashes = grounded_hashes.dup
ungrounded_hashes = {}
hash_needed = true
Expand All @@ -192,7 +202,7 @@ def self.hash_nodes(statements, nodes, grounded_hashes, canonicalize = false)
starting_grounded_nodes = hashes.size
nodes.each do | node |
unless hashes.member? node
grounded, hash = node_hash_for(node, statements, hashes, canonicalize)
grounded, hash = node_hash_for(node, statements, hashes, canonicalize: canonicalize)
if grounded
hashes[node] = hash
end
Expand Down Expand Up @@ -231,13 +241,13 @@ def self.hash_nodes(statements, nodes, grounded_hashes, canonicalize = false)
# @param [Hash] hashes
# @param [Boolean] canonicalize
# @return [Boolean, String]
def self.node_hash_for(node, statements, hashes, canonicalize)
def self.node_hash_for(node, statements, hashes, canonicalize:)
statement_signatures = []
grounded = true
statements.each do | statement |
if statement.to_quad.include?(node)
statement_signatures << hash_string_for(statement, hashes, node, canonicalize)
statement.to_quad.compact.each do | resource |
if statement.terms.include?(node)
statement_signatures << hash_string_for(statement, hashes, node, canonicalize: canonicalize)
statement.terms.each do | resource |
grounded = false unless grounded?(resource, hashes) || resource == node
end
end
Expand All @@ -251,8 +261,8 @@ def self.node_hash_for(node, statements, hashes, canonicalize)
# string signatures for grounded node elements.
# return [String]
# @private
def self.hash_string_for(statement, hashes, node, canonicalize)
statement.to_quad.map {|r| string_for_node(r, hashes, node, canonicalize)}.join("")
def self.hash_string_for(statement, hashes, node, canonicalize:)
statement.terms.map {|r| string_for_node(r, hashes, node, canonicalize: canonicalize)}.join("")
end

# Returns true if a given node is grounded
Expand All @@ -269,7 +279,7 @@ def self.grounded?(node, hashes)
# nodes will return their hashed form.
# @return [String]
# @private
def self.string_for_node(node, hashes,target, canonicalize)
def self.string_for_node(node, hashes,target, canonicalize:)
case
when node.nil?
""
Expand Down

0 comments on commit 63ae7c4

Please sign in to comment.