├── .codeclimate.yml ├── .github ├── CONTRIBUTING.md ├── dependabot.yml └── workflows │ ├── doc.yml │ ├── publish-gem.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .ruby-version ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── examples ├── canvas.rb ├── examples.rb ├── insel_der_tausend_gefahren.rb ├── north.rb ├── north │ ├── Graph.log │ ├── g.10.0.graphml │ ├── g.10.1.graphml │ ├── g.10.11.graphml │ ├── g.10.12.graphml │ ├── g.10.13.graphml │ ├── g.10.14.graphml │ ├── g.10.15.graphml │ ├── g.10.16.graphml │ ├── g.10.17.graphml │ ├── g.10.19.graphml │ ├── g.10.2.graphml │ ├── g.10.20.graphml │ ├── g.10.22.graphml │ ├── g.10.24.graphml │ ├── g.10.25.graphml │ ├── g.10.27.graphml │ ├── g.10.28.graphml │ ├── g.10.29.graphml │ ├── g.10.3.graphml │ ├── g.10.30.graphml │ ├── g.10.31.graphml │ ├── g.10.34.graphml │ ├── g.10.37.graphml │ ├── g.10.38.graphml │ ├── g.10.39.graphml │ ├── g.10.4.graphml │ ├── g.10.40.graphml │ ├── g.10.41.graphml │ ├── g.10.42.graphml │ ├── g.10.45.graphml │ ├── g.10.46.graphml │ ├── g.10.5.graphml │ ├── g.10.50.graphml │ ├── g.10.56.graphml │ ├── g.10.57.graphml │ ├── g.10.58.graphml │ ├── g.10.6.graphml │ ├── g.10.60.graphml │ ├── g.10.61.graphml │ ├── g.10.62.graphml │ ├── g.10.68.graphml │ ├── g.10.69.graphml │ ├── g.10.7.graphml │ ├── g.10.70.graphml │ ├── g.10.71.graphml │ ├── g.10.72.graphml │ ├── g.10.74.graphml │ ├── g.10.75.graphml │ ├── g.10.78.graphml │ ├── g.10.79.graphml │ ├── g.10.8.graphml │ ├── g.10.80.graphml │ ├── g.10.82.graphml │ ├── g.10.83.graphml │ ├── g.10.85.graphml │ ├── g.10.86.graphml │ ├── g.10.88.graphml │ ├── g.10.89.graphml │ ├── g.10.9.graphml │ ├── g.10.90.graphml │ ├── g.10.91.graphml │ ├── g.10.92.graphml │ ├── g.10.93.graphml │ ├── g.10.94.graphml │ ├── g.12.8.graphml │ └── g.14.9.graphml ├── north2.rb ├── rdep-rgl.rb └── unix.dot ├── images ├── example.jpg ├── module_graph.jpg ├── rgl_modules.png └── styled_graph.png ├── lib └── rgl │ ├── adjacency.rb │ ├── base.rb │ ├── bellman_ford.rb │ ├── bidirectional.rb │ ├── bidirectional_adjacency.rb │ ├── bipartite.rb │ ├── condensation.rb │ ├── connected_components.rb │ ├── dijkstra.rb │ ├── dijkstra_visitor.rb │ ├── dot.rb │ ├── edge_properties_map.rb │ ├── edmonds_karp.rb │ ├── graph_iterator.rb │ ├── graph_visitor.rb │ ├── graph_wrapper.rb │ ├── graphxml.rb │ ├── implicit.rb │ ├── mutable.rb │ ├── path.rb │ ├── path_builder.rb │ ├── prim.rb │ ├── rdot.rb │ ├── topsort.rb │ ├── transitiv_closure.rb │ ├── transitivity.rb │ ├── traversal.rb │ └── version.rb ├── rakelib └── dep_graph.rake ├── rgl.gemspec ├── script └── test_all └── test ├── bellman_ford_test.rb ├── bidirectional_graph_test.rb ├── bipartite_test.rb ├── components_test.rb ├── cycles_test.rb ├── dijkstra_issue24_test.rb ├── dijkstra_test.rb ├── directed_graph_test.rb ├── dot_test.rb ├── edge_properties_map_test.rb ├── edge_test.rb ├── edmonds_karp_test.rb ├── graph_test.rb ├── graph_xml_test.rb ├── implicit_test.rb ├── path_test.rb ├── prim_test.rb ├── rdot_test.rb ├── test_helper.rb ├── transitivity_test.rb ├── traversal_bfs_require.rb ├── traversal_test.rb └── undirected_graph_test.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: true 4 | config: 5 | languages: 6 | - ruby 7 | exclude_paths: 8 | - examples/**/* 9 | - script/**/* 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Ruby Graph Library 2 | 3 | Thank you for your interest in contributing to rgl! We welcome all 4 | contributions, whether they're big or small. Here are some guidelines to get you 5 | started. 6 | 7 | ## Code of Conduct 8 | 9 | Please note that this project is released with a Contributor Code of Conduct. By 10 | participating in this project you agree to abide by its terms. 11 | 12 | ## How to Contribute 13 | 14 | 1. Fork the repository. 15 | 2. Create a branch for your changes: `git checkout -b my-feature-branch` 16 | 3. Make your changes and commit them with descriptive commit messages. 17 | 4. Ensure that tests pass by running `bundle exec rake` locally. 18 | 5. Push your changes to your fork: `git push origin my-feature-branch` 19 | 6. Submit a pull request with your changes. 20 | 21 | ## Commit Guidelines 22 | 23 | We follow the [Conventional 24 | Commits](https://www.conventionalcommits.org/en/v1.0.0/) guidelines for commit 25 | messages in this repository. Please ensure that all commit messages follow the 26 | format: 27 | 28 | ``` 29 | [optional scope]: 30 | 31 | [optional body] 32 | 33 | [optional footer(s)] 34 | ``` 35 | 36 | Where: 37 | 38 | - ``: The type of change being made (e.g. feat, fix, docs, style, refactor, test, chore) 39 | - `` (optional): The scope of the change (e.g. component name, file name) 40 | - ``: A brief description of the change 41 | - `[optional body]`: A more detailed description of the change 42 | - `[optional footer(s)]`: Any important information related to the change, such 43 | as a breaking change note 44 | 45 | By following these guidelines, it will be easier to understand the purpose of 46 | each commit and track changes over time. 47 | 48 | Please note that we may ask you to amend your commit message(s) if they do not 49 | follow these guidelines. 50 | 51 | ## Issue Tracker 52 | 53 | If you find a bug or want to request a new feature, please create an issue in 54 | the GitHub issue tracker. Please provide as much detail as possible, including 55 | steps to reproduce the issue (if applicable). 56 | 57 | ## Code Reviews 58 | 59 | All submissions, including submissions by project members, require review. We 60 | use GitHub pull requests for this purpose. Consult [GitHub 61 | Help](https://help.github.com/articles/about-pull-requests/) for more 62 | information on using pull requests. 63 | 64 | ## License 65 | 66 | By contributing, you agree that your contributions will be licensed under the 67 | {file:../LICENSE}. 68 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | # Maintain dependencies for GitHub Actions 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | 11 | # Maintain dependencies for bundler 12 | - package-ecosystem: "bundler" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish Documentation 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | doc: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | ref: ${{ github.ref }} 30 | - name: Set up Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | bundler-cache: true 34 | - name: Build docs 35 | run: bundle exec rake yard || true 36 | - name: Setup Pages 37 | uses: actions/configure-pages@v5 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | path: ./doc 42 | 43 | deploy: 44 | environment: 45 | name: github-pages 46 | url: ${{ steps.deployment.outputs.page_url }} 47 | runs-on: ubuntu-latest 48 | needs: doc 49 | steps: 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /.github/workflows/publish-gem.yml: -------------------------------------------------------------------------------- 1 | name: Push to rubygems.org 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | rubygems-otp-code: 7 | description: RubyGems OTP code 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | env: 15 | GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_AUTH_TOKEN }} 16 | GEM_HOST_OTP_CODE: ${{ github.event.inputs.rubygems-otp-code }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - uses: ruby/setup-ruby@v1 23 | with: 24 | bundler-cache: true 25 | 26 | - name: config 27 | run: | 28 | git config --global user.name "${GITHUB_ACTOR}" 29 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" 30 | 31 | - name: release 32 | run: bundle exec rake release 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # .github/workflows/release.yml 3 | # Adapted from: https://blog.dennisokeeffe.com/blog/2022-03-19-automating-rubygem-package-releases-with-github-actions 4 | name: Prepare Release 5 | 6 | on: 7 | push: 8 | branches: 9 | - master # only master 10 | 11 | jobs: 12 | release-please: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: GoogleCloudPlatform/release-please-action@v4 16 | id: release 17 | with: 18 | # The release type 19 | release-type: ruby 20 | # A name for the artifact releases are being created for 21 | # which is the name of our gem 22 | package-name: rgl 23 | # Should breaking changes before 1.0.0 produce minor bumps? 24 | bump-minor-pre-major: true 25 | # Path to our version file to increment 26 | version-file: "lib/rgl/version.rb" 27 | 28 | # Checkout code if release was created 29 | - uses: actions/checkout@v4 30 | if: ${{ steps.release.outputs.release_created }} 31 | # Setup ruby if a release was created 32 | - uses: ruby/setup-ruby@v1 33 | with: 34 | # runs 'bundle install' and caches installed gems automatically 35 | bundler-cache: true 36 | if: ${{ steps.release.outputs.release_created }} 37 | - name: Setup Graphviz 38 | uses: ts-graphviz/setup-graphviz@v2 39 | if: ${{ steps.release.outputs.release_created }} 40 | - name: Install dependencies 41 | run: | 42 | bundle install --jobs 4 --retry 3 43 | if: ${{ steps.release.outputs.release_created }} 44 | - name: Run tests 45 | run: | 46 | bundle exec rake test 47 | if: ${{ steps.release.outputs.release_created }} 48 | 49 | - name: Build docs 50 | run: | 51 | bundle exec rake yard || true 52 | if: ${{ steps.release.outputs.release_created }} 53 | # Publishing to rubygems.org is done in publish-gem.yml in a manual step. Needs OTP. 54 | # - name: publish gem 55 | # run: | 56 | # mkdir -p $HOME/.gem 57 | # touch $HOME/.gem/credentials 58 | # chmod 0600 $HOME/.gem/credentials 59 | # printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 60 | # gem build *.gemspec 61 | # gem push *.gem 62 | # env: 63 | # GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}" 64 | # if: ${{ steps.release.outputs.release_created }} 65 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build + Test 3 | on: 4 | push: 5 | branches: 6 | - "**" # matches every branch 7 | - "!master" # excludes master 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | ruby-version: 15 | - '2.6' 16 | - '2.7' 17 | - '3.0' 18 | - '3.1' 19 | - '3.2' 20 | - ruby-head 21 | - jruby-head 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: ${{ matrix.ruby-version }} 29 | # runs 'bundle install' and caches installed gems automatically 30 | bundler-cache: true 31 | - name: Setup Graphviz 32 | uses: ts-graphviz/setup-graphviz@v2 33 | - name: Deps 34 | run: bundle install --jobs 4 --retry 3 35 | - name: Run tests 36 | run: bundle exec rake test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /doc 3 | /pkg 4 | /rgl 5 | /TAGS 6 | /tags 7 | graph.dot 8 | .project 9 | .yardoc 10 | Gemfile.lock 11 | /.buildpath 12 | /dep_graph.dot 13 | /graph.* 14 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | # Install graphviz 4 | RUN sudo apt-get update --fix-missing \ 5 | && sudo apt-get install -y graphviz 6 | 7 | USER gitpod 8 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | tasks: 4 | - init: bundle install 5 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.2.0 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --readme README.md 2 | --title 'RGL API Documentation' 3 | --charset utf-8 4 | --embed-mixins 5 | --asset images 6 | --no-private 7 | - 8 | examples/*.rb 9 | rakelib/dep_graph.rake 10 | CHANGELOG.md 11 | LICENSE 12 | .github/*.md 13 | 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem "simplecov" 7 | gem "codeclimate-test-reporter", "~> 1.0.0" 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a) place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b) use the modified software only within your corporation or 18 | organization. 19 | 20 | c) give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d) make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a) distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b) accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c) give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d) make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require 'bundler/setup' 4 | require 'rake/testtask' 5 | require 'rake/clean' 6 | require 'yard' 7 | 8 | $:.unshift File.join(File.dirname(__FILE__), 'lib') 9 | require 'rgl/version' # defines RGL::VERSION 10 | 11 | SOURCES = FileList['lib/**/*.rb'] 12 | 13 | # The default task is run if rake is given no explicit arguments. 14 | desc 'Default Task' 15 | task :default => :test 16 | 17 | # Define a test task. 18 | 19 | Rake::TestTask.new do |t| 20 | t.libs << 'test' 21 | t.pattern = 'test/*_test.rb' 22 | t.verbose = true 23 | end 24 | 25 | # Test bfs_search_tree_from in isolation, to ensure that adjacency is not loaded by other tests. 26 | Rake::TestTask.new do |t| 27 | t.libs << 'test' 28 | t.pattern = 'test/traversal_bfs_require.rb' 29 | t.verbose = true 30 | end 31 | 32 | # Git tagging 33 | 34 | desc 'Commit all changes as a new version commit. Tag the commit with v tag' 35 | task :tag do 36 | puts "Committing and tagging version #{RGL::VERSION}" 37 | `git commit -am 'Version #{RGL::VERSION}'` 38 | `git tag 'v#{RGL::VERSION}'` 39 | end 40 | 41 | YARD::Rake::YardocTask.new 42 | 43 | # Tasks for building and installing RGL gem. 44 | 45 | Bundler::GemHelper.install_tasks 46 | 47 | # TAGS --------------------------------------------------------------- 48 | 49 | file 'tags' => SOURCES do 50 | print 'Running ctags...' 51 | sh %(ctags #{SOURCES.join(' ')}) # vi tags 52 | puts 'done.' 53 | end 54 | 55 | file 'TAGS' => SOURCES do 56 | sh %(etags #{SOURCES.join(' ')}) # emacs TAGS 57 | end 58 | 59 | # Misc tasks ========================================================= 60 | 61 | def count_lines(filename) 62 | lines = 0 63 | codelines = 0 64 | open(filename) { |f| 65 | f.each do |line| 66 | lines += 1 67 | next if line =~ /^\s*$/ 68 | next if line =~ /^\s*#/ 69 | codelines += 1 70 | end 71 | } 72 | [lines, codelines] 73 | end 74 | 75 | def show_line(msg, lines, loc) 76 | printf "%6s %6s %s\n", lines.to_s, loc.to_s, msg 77 | end 78 | 79 | desc 'Count lines in the main files' 80 | task :lines do 81 | total_lines = 0 82 | total_code = 0 83 | show_line('File Name', 'LINES', 'LOC') 84 | SOURCES.each do |fn| 85 | lines, codelines = count_lines(fn) 86 | show_line(fn, lines, codelines) 87 | total_lines += lines 88 | total_code += codelines 89 | end 90 | show_line('TOTAL', total_lines, total_code) 91 | end 92 | -------------------------------------------------------------------------------- /examples/canvas.rb: -------------------------------------------------------------------------------- 1 | # From c.l.r SNIP IT: bond TkCanvas with RubyGraphLibrary 2 | # author: Phlip (see also 3 | # https://www.rubygarden.org/ruby?RubyAlgorithmPackage/TkCanvasSample) 4 | # 5 | # put a GraphViz graph into a TkCanvas, and make nodes 6 | # selectable. Illustrates a bug in GraphViz 7 | 8 | require 'rgl/graphxml' 9 | require 'rgl/adjacency' 10 | require 'rgl/dot' 11 | require 'tk' 12 | 13 | include RGL 14 | filename = ARGV[0] 15 | puts 'Displaying ' + filename 16 | 17 | # ruby canvas.rb north/g.10.8.graphml & 18 | # ruby canvas.rb north/g.12.8.graphml & 19 | # ruby canvas.rb north/g.14.9.graphml & 20 | 21 | File.open(filename) { |file| 22 | graph = DirectedAdjacencyGraph.from_graphxml(file) 23 | graph.write_to_graphic_file('gif', filename) 24 | graph.write_to_graphic_file('plain', filename) 25 | root = TkRoot.new { title "Ex1" } 26 | 27 | canvas = TkCanvas.new(root) { 28 | width 400 29 | height 600 30 | } 31 | canvas.pack() 32 | ovals = [] 33 | 34 | TkcImage.new(canvas, 0, 0) { 35 | anchor 'nw' 36 | 37 | image TkPhotoImage.new() { 38 | file filename + '.gif' 39 | } 40 | } 41 | 42 | # read the 'plain' file, and for each node put an invisible 43 | # oval over its image 44 | 45 | File.open(filename + '.plain') { |f| 46 | graphLine = f.readline() 47 | graphStats = graphLine.split() 48 | graphHeight = graphStats[3].to_f() 49 | nodeLine = f.readline() 50 | fields = nodeLine.split() 51 | 52 | while fields[0] == 'node' 53 | namer = fields[1] 54 | 55 | # the following crud is because GraphViz has no system to 56 | # emit a "plain" format in pixels that exactly match the 57 | # locations of objects in dot's raster output 58 | 59 | # furtherless, the current GraphViz seems to be centering 60 | # the raster output but not the 'plain' output. Hence on 61 | # g.10.8.graphml the X fudge factor must be 45. >sigh< 62 | 63 | # YMMV, based on your system's opinion of the size of an inch 64 | 65 | exx = fields[2].to_f * 96 - 20 # 45 66 | why = (graphHeight - fields[3].to_f) * 96 - 20 67 | widt = fields[4].to_f() * 90 68 | hite = fields[5].to_f() * 90 69 | 70 | ov = TkcOval.new(canvas, exx, why, exx + widt, why + hite) { 71 | state 'hidden' 72 | width 4 73 | outline 'green' 74 | tags namer 75 | } 76 | ovals.push(ov) 77 | nodeLine = f.readline() 78 | fields = nodeLine.split() 79 | end 80 | } 81 | lastOval = ovals[0] 82 | 83 | # at click time, search for an oval in range and display it 84 | 85 | canvas.bind('Button-1') do |event| 86 | x, y = canvas.canvasx(event.x), canvas.canvasy(event.y) 87 | 88 | ovals.each { |r| 89 | x1, y1, x2, y2 = r.coords() 90 | 91 | if x >= x1 and x <= x2 and y >= y1 and y <= y2 92 | lastOval.configure('state' => 'hidden') 93 | lastOval = r 94 | lastOval.configure('state' => 'normal') 95 | break 96 | end 97 | } 98 | end 99 | 100 | Tk.mainloop 101 | } 102 | 103 | -------------------------------------------------------------------------------- /examples/examples.rb: -------------------------------------------------------------------------------- 1 | # Some graph examples 2 | 3 | require 'rgl/adjacency' 4 | require 'rgl/implicit' 5 | 6 | # partite 8, 5 7 | def partite(n, m) 8 | result = RGL::DirectedAdjacencyGraph.new 9 | 1.upto(n) { |i| 10 | 1.upto(m) { |j| 11 | result.add_edge('a' + i.to_s, 'b' + j.to_s) 12 | } 13 | } 14 | result 15 | end 16 | 17 | # modulo(30, 5).dotty 18 | def modulo (n, m) 19 | result = RGL::AdjacencyGraph.new 20 | 1.upto(n) { |x| 21 | 1.upto(n) { |y| 22 | result.add_edge x, y if x != y && x % m == y % m } 23 | } 24 | result 25 | end 26 | 27 | # Cyclic graph with _n_ vertices 28 | def cycle (n) 29 | RGL::ImplicitGraph.new { |g| 30 | g.vertex_iterator { |b| 0.upto(n - 1, &b) } 31 | g.adjacent_iterator { |x, b| b.call((x + 1) % n) } 32 | g.directed = true 33 | } 34 | end 35 | 36 | # Complete Graph with _n_ vertices 37 | def complete (n) 38 | set = n.integer? ? (1..n) : n 39 | RGL::ImplicitGraph.new { |g| 40 | g.vertex_iterator { |b| set.each(&b) } 41 | g.adjacent_iterator { |x, b| 42 | set.each { |y| b.call(y) unless x == y } 43 | } 44 | } 45 | end 46 | 47 | # Directed graph of ruby modules. Edges are defined by the method _ancestors_ 48 | def module_graph 49 | RGL::ImplicitGraph.new { |g| 50 | g.vertex_iterator { |b| 51 | ObjectSpace.each_object(Module, &b) 52 | } 53 | g.adjacent_iterator { |x, b| 54 | x.ancestors.each { |y| 55 | b.call(y) unless x == y || y == Kernel || y == Object 56 | } 57 | } 58 | g.directed = true 59 | } 60 | end 61 | 62 | # Shows graph of divisors of all integers from 2 to _n_. 63 | def divisors(n) 64 | RGL::ImplicitGraph.new { |g| 65 | g.vertex_iterator { |b| 2.upto(n, &b) } 66 | g.adjacent_iterator { |x, b| 67 | n.downto(x + 1) { |y| b.call(y) if y % x == 0 } 68 | } 69 | g.directed = true 70 | } 71 | end 72 | 73 | def bfs_example(g = cycle(5), start = g.detect { |x| true }) 74 | require 'rgl/traversal' 75 | 76 | g.bfs_search_tree_from(start) 77 | end 78 | 79 | # Would like to have GraphXML here 80 | def graph_from_dotfile(file) 81 | g = RGL::AdjacencyGraph.new 82 | pattern = /\s*([^\"]+)[\"\s]*--[\"\s]*([^\"\[\;]+)/ # ugly but works 83 | IO.foreach(file) { |line| 84 | case line 85 | when /^digraph/ 86 | g = RGL::DirectedAdjacencyGraph.new 87 | pattern = /\s*([^\"]+)[\"\s]*->[\"\s]*([^\"\[\;]+)/ 88 | when pattern 89 | g.add_edge $1, $2 90 | else 91 | nil 92 | end 93 | } 94 | g 95 | end 96 | 97 | # ruby -Ilib examples/examples.rb 98 | if $0 == __FILE__ 99 | require 'rgl/dot' 100 | 101 | dg = RGL::DirectedAdjacencyGraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] 102 | dg.write_to_graphic_file 103 | 104 | # BFS tree from 1 of dg: 105 | bfs_example(dg, 1).write_to_graphic_file('png', 'bfs_example') 106 | 107 | # Unix history as a graph: 108 | g = bfs_example(graph_from_dotfile('examples/unix.dot'), 'Interdata') 109 | g.write_to_graphic_file('png', 'unix', { 'label' => 'Interdata Nachfolger', 'fontsize' => 12 }) 110 | 111 | # Modules included by AdjacencyGraph: 112 | g = module_graph 113 | tree = bfs_example(module_graph, RGL::AdjacencyGraph) 114 | g = g.vertices_filtered_by { |v| tree.has_vertex? v } 115 | g.write_to_graphic_file('png', 'module_graph') 116 | end 117 | -------------------------------------------------------------------------------- /examples/insel_der_tausend_gefahren.rb: -------------------------------------------------------------------------------- 1 | # Die Insel der 1000 Gefahren 2 | # https://www.amazon.de/1000-Gefahren-Die-Insel/dp/3473520225/ref=pd_sim_b?ie=UTF8&qid=1203279845&sr=8-3 3 | 4 | require 'rgl/adjacency' 5 | require 'rgl/implicit' 6 | require 'rgl/dot' 7 | 8 | g = RGL::DirectedAdjacencyGraph[ 9 | 8, 9, 10 | 8, 10, 11 | 12 | 9, 11, 13 | 9, 12, 14 | 15 | 10, 13, 16 | 10, 14, 17 | 18 | 11, 15, 19 | 11, 16, 20 | 21 | 12, 17, 22 | 12, 18, 23 | 24 | 13, 19, 25 | 13, 20, 26 | 27 | 14, 21, 28 | 14, 22, 29 | 30 | 15, 23, 31 | 15, 24, 32 | 33 | 16, 25, 34 | 16, 26, 35 | 36 | 17, 27, 37 | 17, 28, 38 | 39 | 18, 29, 40 | 18, 30, 41 | 42 | 18, 31, 43 | 18, 32, 44 | 45 | 19, 31, 46 | 19, 32, 47 | 48 | 20, 33, 49 | 20, 34, 50 | 51 | 21, 35, 52 | 21, 36, 53 | 54 | 22, 37, 55 | 22, 38, 56 | 57 | 23, 39, 58 | 23, 40, 59 | 60 | 24, 41, 61 | 24, 42, 62 | 63 | 25, 43, 64 | 25, 44, 65 | 66 | 26, 45, 67 | 26, 46, 68 | 69 | 27, 47, 70 | 27, 48, 71 | 72 | 28, 49, 73 | 28, 50, 74 | 75 | 29, 51, 76 | 29, 52, 77 | 78 | 30, 53, 79 | 30, 54, 80 | 81 | 31, 55, 82 | 31, 56, 83 | 84 | 32, 57, 85 | 32, 58, 86 | 87 | 33, 59, 88 | 33, 60, 89 | 90 | 34, 61, 91 | 34, 62, 92 | 93 | 35, 63, 94 | 35, 64, 95 | 96 | 36, 65, 97 | 36, 66, 98 | 99 | 37, 67, 100 | 101 | 38, 13, 102 | 103 | 39, 68, 104 | 39, 69, 105 | 106 | 40, 70, 107 | 40, 71, 108 | 109 | 42, 72, 110 | 42, 73, 111 | 112 | 43, 74, 113 | 43, 75, 114 | 115 | 44, 76, 116 | 44, 77, 117 | 118 | 46, 78, 119 | 46, 79, 120 | 121 | 47, 80, 122 | 47, 81, 123 | 124 | 48, 82, 125 | 48, 83, 126 | 127 | 50, 84, 128 | 50, 85, 129 | 130 | 51, 86, 131 | 51, 87, 132 | 133 | 53, 90, 134 | 53, 91, 135 | 136 | 55, 93, 137 | 55, 94, 138 | 139 | 140 | 57, 92, 141 | 142 | 143 | 58, 97, 144 | 58, 98, 145 | 146 | 59, 99, 147 | 99, 100, 148 | 149 | 60, 100, 150 | 151 | 152 | 61, 101, 153 | 61, 102, 154 | 61, 103, 155 | 156 | 62, 104, 157 | 62, 105, 158 | 159 | 64, 88, 160 | 161 | 65, 106, 162 | 65, 107, 163 | 164 | 66, 9, 165 | 166 | 67, 108, 167 | 67, 109, 168 | 169 | 75, 9, 170 | 171 | 78, 26, 172 | 173 | 86, 30, 174 | 175 | 88, 106, 176 | 88, 89, 177 | 178 | 89, 16, 179 | 180 | 92, 95, 181 | 92, 96, 182 | 183 | 93, 37, 184 | 185 | 96, 9, 186 | 187 | 105, 9, 188 | 105, 10] 189 | g.dotty 190 | -------------------------------------------------------------------------------- /examples/north.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/graphxml' 2 | require 'rgl/adjacency' 3 | require 'rgl/dot' 4 | 5 | include RGL 6 | 7 | Dir['north/*.graphml'].each do |filename| 8 | File.open(filename) { |file| 9 | graph = DirectedAdjacencyGraph.new.from_graphxml(file) 10 | graph.write_to_graphic_file('jpg', filename) 11 | } 12 | end 13 | -------------------------------------------------------------------------------- /examples/north/g.10.0.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/north/g.10.1.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/north/g.10.11.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/north/g.10.12.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/north/g.10.13.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/north/g.10.14.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/north/g.10.15.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.16.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.17.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.19.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/north/g.10.2.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/north/g.10.20.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/north/g.10.22.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/north/g.10.24.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/north/g.10.25.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/north/g.10.27.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/north/g.10.28.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/north/g.10.29.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/north/g.10.3.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.30.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.31.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/north/g.10.34.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/north/g.10.37.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/north/g.10.38.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/north/g.10.39.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/north/g.10.4.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.40.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/north/g.10.41.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/north/g.10.42.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.45.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/north/g.10.46.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.5.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/north/g.10.50.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/north/g.10.56.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/north/g.10.57.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.58.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.6.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.60.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.61.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.62.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.68.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/north/g.10.69.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.7.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/north/g.10.70.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.71.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/north/g.10.72.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/north/g.10.74.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/north/g.10.75.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/north/g.10.78.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/north/g.10.79.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.8.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/north/g.10.80.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.82.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/north/g.10.83.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.85.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.86.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.10.88.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/north/g.10.89.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/north/g.10.9.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.90.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.91.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/north/g.10.92.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/north/g.10.93.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/north/g.10.94.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/north/g.12.8.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/north/g.14.9.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/north2.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/graphxml' 2 | require 'rgl/adjacency' 3 | require 'rgl/dot' 4 | 5 | include RGL 6 | name, nnodes, nedges = '', '', '' 7 | 8 | IO.foreach('north/Graph.log') { |line| 9 | if /name:\s*(.*)\sformat: graphml\s+nodes: (\d+)\s+edges: (\d+)/ =~ line 10 | name, nnodes, nedges = $1, $2.to_i, $3.to_i 11 | end 12 | 13 | if name && /directed: (.*)\s+acyclic: (.*)\s+.*connected: (.*)\s+biconnected: (.*)\s+/ =~ line 14 | directed, acyclic, connected, biconnected = $1, $2, $3, $4 15 | puts [name, nnodes, nedges].join('-|-') 16 | File.open('north/' + name + '.graphml') { |file| 17 | graph = DirectedAdjacencyGraph.from_graphxml(file) 18 | puts "#{graph.num_vertices} = #{nnodes}" 19 | } 20 | end 21 | } 22 | -------------------------------------------------------------------------------- /examples/unix.dot: -------------------------------------------------------------------------------- 1 | /* courtesy Ian Darwin and Geoff Collyer, Softquad Inc. */ 2 | digraph unix { 3 | size="6,6"; 4 | "5th Edition" -> "6th Edition"; 5 | "5th Edition" -> "PWB 1.0"; 6 | "6th Edition" -> "LSX"; 7 | "6th Edition" -> "1 BSD"; 8 | "6th Edition" -> "Mini Unix"; 9 | "6th Edition" -> "Wollongong"; 10 | "6th Edition" -> "Interdata"; 11 | "Interdata" -> "Unix/TS 3.0"; 12 | "Interdata" -> "PWB 2.0"; 13 | "Interdata" -> "7th Edition"; 14 | "7th Edition" -> "8th Edition"; 15 | "7th Edition" -> "32V"; 16 | "7th Edition" -> "V7M"; 17 | "7th Edition" -> "Ultrix-11"; 18 | "7th Edition" -> "Xenix"; 19 | "7th Edition" -> "UniPlus+"; 20 | "V7M" -> "Ultrix-11"; 21 | "8th Edition" -> "9th Edition"; 22 | "1 BSD" -> "2 BSD"; 23 | "2 BSD" -> "2.8 BSD"; 24 | "2.8 BSD" -> "Ultrix-11"; 25 | "2.8 BSD" -> "2.9 BSD"; 26 | "32V" -> "3 BSD"; 27 | "3 BSD" -> "4 BSD"; 28 | "4 BSD" -> "4.1 BSD"; 29 | "4.1 BSD" -> "4.2 BSD"; 30 | "4.1 BSD" -> "2.8 BSD"; 31 | "4.1 BSD" -> "8th Edition"; 32 | "4.2 BSD" -> "4.3 BSD"; 33 | "4.2 BSD" -> "Ultrix-32"; 34 | "PWB 1.0" -> "PWB 1.2"; 35 | "PWB 1.0" -> "USG 1.0"; 36 | "PWB 1.2" -> "PWB 2.0"; 37 | "USG 1.0" -> "CB Unix 1"; 38 | "USG 1.0" -> "USG 2.0"; 39 | "CB Unix 1" -> "CB Unix 2"; 40 | "CB Unix 2" -> "CB Unix 3"; 41 | "CB Unix 3" -> "Unix/TS++"; 42 | "CB Unix 3" -> "PDP-11 Sys V"; 43 | "USG 2.0" -> "USG 3.0"; 44 | "USG 3.0" -> "Unix/TS 3.0"; 45 | "PWB 2.0" -> "Unix/TS 3.0"; 46 | "Unix/TS 1.0" -> "Unix/TS 3.0"; 47 | "Unix/TS 3.0" -> "TS 4.0"; 48 | "Unix/TS++" -> "TS 4.0"; 49 | "CB Unix 3" -> "TS 4.0"; 50 | "TS 4.0" -> "System V.0"; 51 | "System V.0" -> "System V.2"; 52 | "System V.2" -> "System V.3"; 53 | } 54 | -------------------------------------------------------------------------------- /images/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monora/rgl/4d689104a6740269fd8fa8f07c95dff646dc5e52/images/example.jpg -------------------------------------------------------------------------------- /images/module_graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monora/rgl/4d689104a6740269fd8fa8f07c95dff646dc5e52/images/module_graph.jpg -------------------------------------------------------------------------------- /images/rgl_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monora/rgl/4d689104a6740269fd8fa8f07c95dff646dc5e52/images/rgl_modules.png -------------------------------------------------------------------------------- /images/styled_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monora/rgl/4d689104a6740269fd8fa8f07c95dff646dc5e52/images/styled_graph.png -------------------------------------------------------------------------------- /lib/rgl/bellman_ford.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/dijkstra_visitor' 2 | require 'rgl/edge_properties_map' 3 | require 'rgl/path_builder' 4 | 5 | module RGL 6 | 7 | # Bellman-Ford shortest paths algorithm has the following event points: 8 | # 9 | # * examine_edge 10 | # * edge_relaxed 11 | # * edge_not_relaxed 12 | # * edge_minimized 13 | # * edge_not_minimized 14 | # 15 | class BellmanFordVisitor < DijkstraVisitor 16 | 17 | def_event_handlers :edge_minimized, :edge_not_minimized 18 | 19 | def initialize(graph) 20 | super(graph) 21 | 22 | # by default, through an exception if a negative-weight cycle is detected 23 | @edge_not_minimized_event_handler = lambda do |u, v| 24 | raise ArgumentError.new("there is a negative-weight cycle including edge (#{u}, #{v})") 25 | end 26 | end 27 | 28 | end 29 | 30 | # This class implements {Graph#bellman_ford_shortest_paths}. 31 | class BellmanFordAlgorithm 32 | 33 | # Initializes Bellman-Ford algorithm for a _graph_ with provided edges weights map. 34 | # 35 | def initialize(graph, edge_weights_map, visitor) 36 | @graph = graph 37 | @edge_weights_map = EdgePropertiesMap.new(edge_weights_map, @graph.directed?) 38 | @visitor = visitor 39 | end 40 | 41 | # Finds the shortest path form the _source_ to every other vertex of the graph. 42 | # 43 | # Returns the shortest paths map that contains the shortest path (if it 44 | # exists) from the source to any vertex of the graph. 45 | # 46 | # @return [Hash[Object,Array]] 47 | def shortest_paths(source) 48 | init(source) 49 | relax_edges 50 | PathBuilder.new(source, @visitor.parents_map).paths(@graph.vertices) 51 | end 52 | 53 | private 54 | 55 | def init(source) 56 | @visitor.set_source(source) 57 | end 58 | 59 | def relax_edges 60 | (@graph.size - 1).times do 61 | @graph.each_edge do |u, v| 62 | relax_edge(u, v) 63 | relax_edge(v, u) unless @graph.directed? 64 | end 65 | end 66 | 67 | @graph.each_edge do |u, v| 68 | if @visitor.distance_map[u] + @edge_weights_map.edge_property(u, v) < @visitor.distance_map[v] 69 | @visitor.handle_edge_not_minimized(u, v) 70 | else 71 | @visitor.handle_edge_minimized(u, v) 72 | end 73 | end 74 | end 75 | 76 | def relax_edge(u, v) 77 | @visitor.handle_examine_edge(u, v) 78 | 79 | new_v_distance = @visitor.distance_map[u] + @edge_weights_map.edge_property(u, v) 80 | 81 | if new_v_distance < @visitor.distance_map[v] 82 | @visitor.distance_map[v] = new_v_distance 83 | @visitor.parents_map[v] = u 84 | 85 | @visitor.handle_edge_relaxed(u, v) 86 | else 87 | @visitor.handle_edge_not_relaxed(u, v) 88 | end 89 | end 90 | 91 | end # class BellmanFordAlgorithm 92 | 93 | 94 | module Graph 95 | 96 | # Finds the shortest paths from the _source_ to each vertex of the graph. 97 | # 98 | # Returns a Hash that maps each vertex of the graph to an Array of vertices that represents the shortest path 99 | # from the _source_ to the vertex. If the path doesn't exist, the corresponding hash value is nil. For the _source_ 100 | # vertex returned hash contains a trivial one-vertex path - [source]. 101 | # 102 | # Unlike Dijkstra algorithm, Bellman-Ford shortest paths algorithm works with negative edge weights. 103 | # 104 | # Raises ArgumentError if an edge weight is undefined. 105 | # 106 | # Raises ArgumentError or the graph has negative-weight cycles. This behavior can be overridden my a custom handler 107 | # for visitor's _edge_not_minimized_ event. 108 | # @return [Hash[Object,Array]] 109 | def bellman_ford_shortest_paths(edge_weights_map, source, visitor = BellmanFordVisitor.new(self)) 110 | BellmanFordAlgorithm.new(self, edge_weights_map, visitor).shortest_paths(source) 111 | end 112 | 113 | end # module Graph 114 | 115 | end # module RGL 116 | -------------------------------------------------------------------------------- /lib/rgl/bidirectional.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/base' 2 | 3 | module RGL 4 | 5 | # BGL defines the concept BidirectionalGraph as follows: 6 | # 7 | # The BidirectionalGraph concept refines IncidenceGraph and adds the 8 | # requirement for efficient access to the in-edges of each vertex. This 9 | # concept is separated from IncidenceGraph because, for directed graphs, 10 | # efficient access to in-edges typically requires more storage space, 11 | # and many algorithms do not require access to in-edges. For undirected 12 | # graphs, this is not an issue; because the in_edges() and out_edges() 13 | # functions are the same, they both return the edges incident to the vertex. 14 | # 15 | module BidirectionalGraph 16 | 17 | include Graph 18 | 19 | # Iterator providing access to the in-edges (for directed graphs) or incident 20 | # edges (for undirected graphs) of vertex _v_. For both directed and 21 | # undirected graphs, the target of an out-edge is required to be vertex _v_ 22 | # and the source is required to be a vertex that is adjacent to _v_. 23 | # 24 | def each_in_neighbor(v) 25 | raise NotImplementedError 26 | yield u 27 | end 28 | 29 | alias :each_out_neighbor :each_adjacent 30 | 31 | def has_in_edge?(u, v) 32 | raise NotImplementedError 33 | end 34 | 35 | alias :has_out_edge? :has_edge? 36 | 37 | def in_neighbors(v) 38 | raise NotImplementedError 39 | end 40 | 41 | alias :out_neighbors :adjacent_vertices 42 | 43 | # Returns the number of in-edges (for directed graphs) or the number of 44 | # incident edges (for undirected graphs) of vertex _v_. 45 | # @return [int] 46 | def in_degree(v) 47 | r = 0 48 | each_in_neighbor(v) { |u| r += 1 } 49 | r 50 | end 51 | 52 | # Returns the number of in-edges plus out-edges (for directed graphs) or the 53 | # number of incident edges (for undirected graphs) of vertex _v_. 54 | # @return [int] 55 | def degree(v) 56 | in_degree(v) + out_degree(v) 57 | end 58 | 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /lib/rgl/bidirectional_adjacency.rb: -------------------------------------------------------------------------------- 1 | # bidirectional_adjacency.rb 2 | # 3 | require 'rgl/adjacency' 4 | require 'rgl/bidirectional' 5 | 6 | module RGL 7 | 8 | # This implementation of {BidirectionalGraph} creates an internal 9 | # {DirectedAdjacencyGraph} to store the in-edges and overrides methods 10 | # to ensure that the out and in graphs remain synchronized. 11 | # 12 | class BidirectionalAdjacencyGraph < DirectedAdjacencyGraph 13 | 14 | include BidirectionalGraph 15 | 16 | # @see DirectedAdjacencyGraph#initialize 17 | # 18 | # In super method the in edges are also added since {add_edge} of this class 19 | # also inserts edges in `@reverse`. 20 | def initialize(edgelist_class = Set, *other_graphs) 21 | @reverse = DirectedAdjacencyGraph.new(edgelist_class) 22 | super(edgelist_class, *other_graphs) 23 | end 24 | 25 | # @see MutableGraph#add_vertex. 26 | def add_vertex(v) 27 | super(v) 28 | @reverse.add_vertex(v) 29 | end 30 | 31 | # @see MutableGraph#add_edge. 32 | def add_edge(u, v) 33 | super(u, v) 34 | @reverse.add_edge(v, u) 35 | end 36 | 37 | # @see MutableGraph#remove_vertex. 38 | def remove_vertex(v) 39 | super(v) 40 | @reverse.remove_vertex(v) 41 | end 42 | 43 | # @see MutableGraph::remove_edge. 44 | def remove_edge(u, v) 45 | super(u, v) 46 | @reverse.remove_edge(v, u) 47 | end 48 | 49 | # @see Graph#has_edge? 50 | def has_in_edge?(u, v) 51 | @reverse.has_edge?(u, v) 52 | end 53 | 54 | alias :has_out_edge? :has_edge? 55 | 56 | # @see BidirectionalGraph#each_in_neighbor 57 | def each_in_neighbor(v, &b) 58 | @reverse.each_adjacent(v, &b) 59 | end 60 | 61 | alias :each_out_neighbor :each_adjacent 62 | 63 | def in_neighbors(v) 64 | @reverse.adjacent_vertices(v) 65 | end 66 | 67 | # Returns the number of in-edges (for directed graphs) or the number of 68 | # incident edges (for undirected graphs) of vertex _v_. 69 | # @return [int] 70 | def in_degree(v) 71 | @reverse.out_degree(v) 72 | end 73 | 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/rgl/bipartite.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/base' 2 | require 'rgl/traversal' 3 | 4 | module RGL 5 | 6 | module Graph 7 | 8 | # Separates graph's vertices into two disjoint sets so that every edge of the graph connects vertices from different 9 | # sets. If it's possible, the graph is bipartite. 10 | # 11 | # Returns an array of two disjoint vertices sets (represented as arrays) if the graph is bipartite. Otherwise, 12 | # returns nil. 13 | # @return [Array] 14 | def bipartite_sets 15 | raise NotUndirectedError.new('bipartite sets can only be found for an undirected graph') if directed? 16 | 17 | bfs = BipartiteBFSIterator.new(self) 18 | 19 | # if necessary, we start BFS from each vertex to make sure 20 | # that all connected components of the graph are processed 21 | each_vertex do |u| 22 | next if bfs.finished_vertex?(u) 23 | 24 | bfs.reset_start(u) 25 | bfs.move_forward_until { bfs.found_odd_cycle } 26 | 27 | return if bfs.found_odd_cycle 28 | end 29 | 30 | bfs.bipartite_sets_map.inject([[], []]) do |sets, (vertex, set)| 31 | sets[set] << vertex 32 | sets 33 | end 34 | end 35 | 36 | # Returns true if the graph is bipartite. Otherwise returns false. 37 | # 38 | def bipartite? 39 | !bipartite_sets.nil? 40 | end 41 | 42 | end # module Graph 43 | 44 | 45 | class BipartiteBFSIterator < BFSIterator 46 | 47 | attr_reader :bipartite_sets_map, :found_odd_cycle 48 | 49 | def reset 50 | super 51 | 52 | @bipartite_sets_map = {} 53 | @found_odd_cycle = false 54 | end 55 | 56 | def set_to_begin 57 | super 58 | 59 | @bipartite_sets_map[@start_vertex] = 0 60 | end 61 | 62 | def reset_start(new_start) 63 | @start_vertex = new_start 64 | set_to_begin 65 | end 66 | 67 | def handle_tree_edge(u, v) 68 | @bipartite_sets_map[v] = (@bipartite_sets_map[u] + 1) % 2 unless u.nil? # put v into the other set 69 | end 70 | 71 | def handle_back_edge(u, v) 72 | verify_odd_cycle(u, v) 73 | end 74 | 75 | def handle_forward_edge(u, v) 76 | verify_odd_cycle(u, v) 77 | end 78 | 79 | private 80 | 81 | def verify_odd_cycle(u, v) 82 | u_set = @bipartite_sets_map[u] 83 | @found_odd_cycle = true if u_set && u_set == @bipartite_sets_map[v] 84 | end 85 | 86 | end # class BipartiteBFSIterator 87 | 88 | end # module RGL 89 | -------------------------------------------------------------------------------- /lib/rgl/condensation.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/base' 2 | require 'rgl/connected_components' 3 | require 'rgl/implicit' 4 | 5 | module RGL 6 | 7 | module Graph 8 | 9 | # Returns an {ImplicitGraph} where the strongly connected components of 10 | # this graph are condensed into single nodes represented by Set instances 11 | # containing the members of each strongly connected component. Edges 12 | # between the different strongly connected components are preserved while 13 | # edges within strongly connected components are omitted. 14 | # 15 | # Raises {NotDirectedError} if run on an undirected graph. 16 | # @return ImplicitGraph 17 | def condensation_graph 18 | raise NotDirectedError, 19 | "condensation_graph only supported for directed graphs" unless directed? 20 | 21 | # Get the component map for the strongly connected components. 22 | comp_map = strongly_connected_components.comp_map 23 | 24 | # Invert the map such that for any number, n, in the component map a Set 25 | # instance is created containing all of the nodes which map to n. The Set 26 | # instances will be used to map to the number, n, with which the elements 27 | # of the set are associated. 28 | inv_comp_map = {} 29 | comp_map.each { |v, n| (inv_comp_map[n] ||= Set.new) << v } 30 | 31 | # Create an ImplicitGraph where the nodes are the strongly connected 32 | # components of this graph and the edges are the edges of this graph which 33 | # cross between the strongly connected components. 34 | ImplicitGraph.new do |g| 35 | g.vertex_iterator do |b| 36 | inv_comp_map.each_value(&b) 37 | end 38 | 39 | g.adjacent_iterator do |scc, b| 40 | scc.each do |v| 41 | each_adjacent(v) do |w| 42 | # Do not make the cluster reference itself in the graph. 43 | if comp_map[v] != comp_map[w] 44 | b.call(inv_comp_map[comp_map[w]]) 45 | end 46 | end 47 | end 48 | end 49 | 50 | g.directed = true 51 | end 52 | end 53 | 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/rgl/connected_components.rb: -------------------------------------------------------------------------------- 1 | # connected_components.rb 2 | # 3 | # This file contains the algorithms for the connected components of an 4 | # undirected graph (each_connected_component) and strongly connected components 5 | # for directed graphs (strongly_connected_components). 6 | # 7 | require 'rgl/traversal' 8 | 9 | module RGL 10 | 11 | module Graph 12 | 13 | # Compute the connected components of an undirected graph, using a 14 | # DFS (Depth-first search)-based approach. A _connected component_ of 15 | # an undirected graph is a set of vertices that are all reachable 16 | # from each other. 17 | # 18 | # The function is implemented as an iterator which calls the client 19 | # with an array of vertices for each component. 20 | # 21 | # It raises an exception if the graph is directed. 22 | # 23 | def each_connected_component 24 | raise NotUndirectedError, 25 | "each_connected_component only works " + 26 | "for undirected graphs." if directed? 27 | 28 | comp = [] 29 | vis = DFSVisitor.new(self) 30 | vis.set_finish_vertex_event_handler { |v| comp << v } 31 | 32 | vis.set_start_vertex_event_handler do |v| 33 | yield comp unless comp.empty? 34 | comp = [] 35 | end 36 | 37 | depth_first_search(vis) { |v| } 38 | yield comp unless comp.empty? 39 | end 40 | 41 | # This {GraphVisitor} is used by {#strongly_connected_components} to compute 42 | # the strongly connected components of a directed graph. 43 | # 44 | class TarjanSccVisitor < DFSVisitor 45 | 46 | attr_reader :comp_map 47 | 48 | # Creates a new TarjanSccVisitor for graph _g_, which should be directed. 49 | # 50 | def initialize(g) 51 | super g 52 | @root_map = {} 53 | @comp_map = {} 54 | @discover_time_map = {} 55 | @dfs_time = 0 56 | @c_index = 0 57 | @stack = [] 58 | end 59 | 60 | def handle_examine_vertex(v) 61 | @root_map[v] = v 62 | @comp_map[v] = -1 63 | @dfs_time += 1 64 | @discover_time_map[v] = @dfs_time 65 | @stack.push(v) 66 | end 67 | 68 | def handle_finish_vertex(v) 69 | # Search adjacent vertex w with earliest discover time 70 | root_v = @root_map[v] 71 | 72 | graph.each_adjacent(v) do |w| 73 | if @comp_map[w] == -1 74 | root_v = min_discover_time(root_v, @root_map[w]) 75 | end 76 | end 77 | 78 | @root_map[v] = root_v 79 | 80 | if root_v == v # v is topmost vertex of a SCC 81 | begin # pop off all vertices until v 82 | w = @stack.pop 83 | @comp_map[w] = @c_index 84 | end until w == v 85 | @c_index += 1 86 | end 87 | end 88 | 89 | # Return the number of components found so far. 90 | # 91 | def num_comp 92 | @c_index 93 | end 94 | 95 | private 96 | 97 | def min_discover_time(u, v) 98 | @discover_time_map[u] < @discover_time_map[v] ? u : v 99 | end 100 | 101 | end # class TarjanSccVisitor 102 | 103 | # This is Tarjan's algorithm for strongly connected components, from his 104 | # paper "Depth first search and linear graph algorithms". It calculates 105 | # the components in a single application of DFS. We implement the 106 | # algorithm with the help of the {DFSVisitor} {TarjanSccVisitor}. 107 | # 108 | # === Definition 109 | # 110 | # A _strongly connected component_ of a directed graph G=(V,E) is a 111 | # maximal set of vertices U which is in V, such that for every pair of 112 | # vertices u and v in U, we have both a path from u to v and a path 113 | # from v to u. That is to say, u and v are reachable from each other. 114 | # 115 | # @Article!{Tarjan:1972:DFS, 116 | # author = "R. E. Tarjan", 117 | # key = "Tarjan", 118 | # title = "Depth First Search and Linear Graph Algorithms", 119 | # journal = "SIAM Journal on Computing", 120 | # volume = "1", 121 | # number = "2", 122 | # pages = "146--160", 123 | # month = jun, 124 | # year = "1972", 125 | # CODEN = "SMJCAT", 126 | # ISSN = "0097-5397 (print), 1095-7111 (electronic)", 127 | # bibdate = "Thu Jan 23 09:56:44 1997", 128 | # bibsource = "Parallel/Multi.bib, Misc/Reverse.eng.bib", 129 | # } 130 | # 131 | # The output of the algorithm is recorded in a {TarjanSccVisitor} _vis_. 132 | # +vis.comp_map+ will contain numbers giving the component ID assigned to 133 | # each vertex. The number of components is +vis.num_comp+. 134 | # @return [TarjanSccVisitor] 135 | def strongly_connected_components 136 | raise NotDirectedError, 137 | "strong_components only works for directed graphs." unless directed? 138 | 139 | vis = TarjanSccVisitor.new(self) 140 | depth_first_search(vis) { |v| } 141 | vis 142 | end 143 | 144 | end # module Graph 145 | 146 | end # module RGL 147 | -------------------------------------------------------------------------------- /lib/rgl/dijkstra.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/dijkstra_visitor' 2 | require 'rgl/edge_properties_map' 3 | require 'rgl/path_builder' 4 | 5 | require 'pairing_heap' 6 | 7 | module RGL 8 | 9 | # This class implements {Graph#dijkstra_shortest_path} and {Graph#dijkstra_shortest_paths} 10 | class DijkstraAlgorithm 11 | 12 | # Distance combinator is a lambda that accepts the distance (usually from the source) to vertex _u_ and the weight 13 | # of the edge connecting vertex _u_ to another vertex _v_ and returns the distance to vertex _v_ if it's reached 14 | # through the vertex _u_. By default, the distance to vertex _u_ and the edge's weight are summed. 15 | DEFAULT_DISTANCE_COMBINATOR = lambda { |distance, edge_weight| distance + edge_weight } 16 | 17 | # Initializes Dijkstra's algorithm for a _graph_ with provided edges weights map. 18 | # 19 | def initialize(graph, edge_weights_map, visitor, distance_combinator = nil) 20 | @graph = graph 21 | @edge_weights_map = build_edge_weights_map(edge_weights_map) 22 | @visitor = visitor 23 | @distance_combinator = distance_combinator || DEFAULT_DISTANCE_COMBINATOR 24 | end 25 | 26 | # Finds the shortest path from the _source_ to the _target_ in the graph. 27 | # 28 | # Returns the shortest path, if it exists, as an Array of vertices. Otherwise, returns nil. 29 | # 30 | def shortest_path(source, target) 31 | init(source) 32 | relax_edges(target, true) 33 | PathBuilder.new(source, @visitor.parents_map).path(target) 34 | end 35 | 36 | # Finds the shortest path form the _source_ to every other vertex of the graph and builds shortest paths map. 37 | # 38 | # Returns the shortest paths map that contains the shortest path (if it exists) from the source to any vertex of the 39 | # graph. 40 | # 41 | def shortest_paths(source) 42 | find_shortest_paths(source) 43 | PathBuilder.new(source, @visitor.parents_map).paths(@graph.vertices) 44 | end 45 | 46 | # Finds the shortest path from the _source_ to every other vertex. 47 | # 48 | def find_shortest_paths(source) 49 | init(source) 50 | relax_edges 51 | end 52 | 53 | private 54 | 55 | def init(source) 56 | @visitor.set_source(source) 57 | 58 | @queue = PairingHeap::MinPriorityQueue.new 59 | @queue.push(source, 0) 60 | end 61 | 62 | def relax_edges(target = nil, break_on_target = false) 63 | until @queue.empty? 64 | u = @queue.pop 65 | 66 | break if break_on_target && u == target 67 | 68 | @visitor.handle_examine_vertex(u) 69 | 70 | @graph.each_adjacent(u) do |v| 71 | relax_edge(u, v) unless @visitor.finished_vertex?(v) 72 | end 73 | 74 | @visitor.color_map[u] = :BLACK 75 | @visitor.handle_finish_vertex(u) 76 | end 77 | end 78 | 79 | def relax_edge(u, v) 80 | @visitor.handle_examine_edge(u, v) 81 | 82 | new_v_distance = @distance_combinator.call(@visitor.distance_map[u], @edge_weights_map.edge_property(u, v)) 83 | 84 | if new_v_distance < @visitor.distance_map[v] 85 | @visitor.distance_map[v] = new_v_distance 86 | @visitor.parents_map[v] = u 87 | 88 | if @visitor.color_map[v] == :WHITE 89 | @visitor.color_map[v] = :GRAY 90 | @queue.push(v, new_v_distance) 91 | elsif @visitor.color_map[v] == :GRAY 92 | @queue.decrease_key(v, new_v_distance) 93 | end 94 | 95 | @visitor.handle_edge_relaxed(u, v) 96 | else 97 | @visitor.handle_edge_not_relaxed(u, v) 98 | end 99 | end 100 | 101 | def build_edge_weights_map(edge_weights_map) 102 | edge_weights_map.is_a?(EdgePropertiesMap) ? edge_weights_map : NonNegativeEdgePropertiesMap.new(edge_weights_map, @graph.directed?) 103 | end 104 | 105 | end # class DijkstraAlgorithm 106 | 107 | 108 | module Graph 109 | 110 | # Finds the shortest path from the _source_ to the _target_ in the graph. 111 | # 112 | # If the path exists, returns it as an Array of vertices. Otherwise, returns nil. 113 | # 114 | # Raises ArgumentError if edge weight is negative or undefined. 115 | # 116 | def dijkstra_shortest_path(edge_weights_map, source, target, visitor = DijkstraVisitor.new(self)) 117 | DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_path(source, target) 118 | end 119 | 120 | # Finds the shortest paths from the _source_ to each vertex of the graph. 121 | # 122 | # Returns a Hash that maps each vertex of the graph to an Array of vertices that represents the shortest path 123 | # from the _source_ to the vertex. If the path doesn't exist, the corresponding hash value is nil. For the _source_ 124 | # vertex returned hash contains a trivial one-vertex path - [source]. 125 | # 126 | # Raises ArgumentError if edge weight is negative or undefined. 127 | # 128 | def dijkstra_shortest_paths(edge_weights_map, source, visitor = DijkstraVisitor.new(self)) 129 | DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_paths(source) 130 | end 131 | 132 | end # module Graph 133 | 134 | end # module RGL 135 | -------------------------------------------------------------------------------- /lib/rgl/dijkstra_visitor.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/base' 2 | require 'rgl/graph_visitor' 3 | 4 | module RGL 5 | 6 | # Dijkstra shortest path algorithm has the following event points: 7 | # 8 | # * examine_vertex 9 | # * examine_edge 10 | # * edge_relaxed 11 | # * edge_not_relaxed 12 | # * finish_vertex 13 | # 14 | class DijkstraVisitor 15 | 16 | include GraphVisitor 17 | 18 | attr_accessor :distance_map, :parents_map 19 | 20 | def_event_handlers :edge_relaxed, :edge_not_relaxed 21 | 22 | # Returns visitor into initial state. 23 | # 24 | def reset 25 | super 26 | 27 | @distance_map = Hash.new(INFINITY) 28 | @parents_map = {} 29 | end 30 | 31 | # Initializes visitor with a new source. 32 | # 33 | def set_source(source) 34 | reset 35 | 36 | color_map[source] = :GRAY 37 | distance_map[source] = 0 38 | end 39 | 40 | end # DijkstraVisitor 41 | 42 | end # RGL -------------------------------------------------------------------------------- /lib/rgl/dot.rb: -------------------------------------------------------------------------------- 1 | # dot.rb 2 | # 3 | # Minimal Dot support, based on Dave Thomas's dot module (included in rdoc). 4 | # rdot.rb is a modified version which also contains support for undirected 5 | # graphs. 6 | # 7 | # You need to have [GraphViz](https://www.graphviz.org) installed, because the 8 | # functions in this modul execute the GraphViz executables _dot_ or _dotty_. 9 | 10 | require 'rgl/rdot' 11 | 12 | module RGL 13 | module Graph 14 | # Returns a label for vertex v. Default is v.to_s 15 | def vertex_label(v) 16 | v.to_s 17 | end 18 | 19 | def vertex_id(v) 20 | v 21 | end 22 | 23 | # Set the configuration values for the given vertex 24 | def set_vertex_options(vertex, **options) 25 | @vertex_options ||= {} 26 | @vertex_options[vertex] ||= {} 27 | 28 | RGL::DOT::NODE_OPTS.each do |opt| 29 | @vertex_options[vertex][:"#{opt}"] = options[:"#{opt}"] if options.key?(:"#{opt}") 30 | end 31 | end 32 | 33 | # Set the configuration values for the given edge 34 | def set_edge_options(u, v, **options) 35 | edge = edge_class.new(u, v) 36 | @edge_options ||= {} 37 | @edge_options[edge] ||= {} 38 | 39 | RGL::DOT::EDGE_OPTS.each do |opt| 40 | @edge_options[edge][:"#{opt}"] = options[:"#{opt}"] if options.key?(:"#{opt}") 41 | end 42 | end 43 | 44 | # Return a {DOT::Digraph} for directed graphs or a {DOT::Graph} for an 45 | # undirected {Graph}. _params_ can contain any graph property specified in 46 | # rdot.rb. 47 | # 48 | def to_dot_graph(params = {}) 49 | params['name'] ||= self.class.name.gsub(/:/, '_') 50 | fontsize = params['fontsize'] ? params['fontsize'] : '8' 51 | graph = (directed? ? DOT::Digraph : DOT::Graph).new(params) 52 | edge_class = directed? ? DOT::DirectedEdge : DOT::Edge 53 | 54 | each_vertex do |v| 55 | default_vertex_options = { 56 | 'name' => vertex_id(v), 57 | 'fontsize' => fontsize, 58 | 'label' => vertex_label(v) 59 | } 60 | each_vertex_options = default_vertex_options 61 | 62 | if @vertex_options && @vertex_options[v] 63 | RGL::DOT::NODE_OPTS.each do |opt| 64 | if @vertex_options[v].key?(:"#{opt}") 65 | each_vertex_options["#{opt}"] = @vertex_options[v].fetch(:"#{opt}") 66 | end 67 | end 68 | end 69 | graph << DOT::Node.new(each_vertex_options) 70 | end 71 | 72 | edges.each do |edge| 73 | default_edge_options = { 74 | 'from' => edge.source, 75 | 'to' => edge.target, 76 | 'fontsize' => fontsize 77 | } 78 | 79 | each_edge_options = default_edge_options 80 | 81 | if @edge_options && @edge_options[edge] 82 | RGL::DOT::EDGE_OPTS.each do |opt| 83 | if @edge_options[edge].key?(:"#{opt}") 84 | each_edge_options["#{opt}"] = @edge_options[edge].fetch(:"#{opt}") 85 | end 86 | end 87 | end 88 | graph << edge_class.new(each_edge_options) 89 | end 90 | 91 | graph 92 | end 93 | 94 | # Output the DOT-graph to stream _s_. 95 | # 96 | def print_dotted_on(params = {}, s = $stdout) 97 | s << to_dot_graph(params).to_s << "\n" 98 | end 99 | 100 | # Call dotty[https://www.graphviz.org] for the graph which is written to the 101 | # file 'graph.dot' in the current directory. 102 | # 103 | def dotty(params = {}) 104 | dotfile = "graph.dot" 105 | File.open(dotfile, "w") do |f| 106 | print_dotted_on(params, f) 107 | end 108 | unless system("dotty", dotfile) 109 | raise "Error executing dotty. Did you install GraphViz?" 110 | end 111 | end 112 | 113 | # Use dot[https://www.graphviz.org] to create a graphical representation of 114 | # the graph. Returns the filename of the graphics file. 115 | # 116 | def write_to_graphic_file(fmt = 'png', dotfile = "graph", options = {}) 117 | src = dotfile + ".dot" 118 | dot = dotfile + "." + fmt 119 | 120 | File.open(src, 'w') do |f| 121 | f << self.to_dot_graph(options).to_s << "\n" 122 | end 123 | 124 | unless system("dot -T#{fmt} #{src} -o #{dot}") 125 | raise "Error executing dot. Did you install GraphViz?" 126 | end 127 | dot 128 | end 129 | 130 | end # module Graph 131 | 132 | end # module RGL 133 | -------------------------------------------------------------------------------- /lib/rgl/edge_properties_map.rb: -------------------------------------------------------------------------------- 1 | module RGL 2 | 3 | class EdgePropertiesMap 4 | 5 | def initialize(edge_properties_map, directed) 6 | @edge_properties_map = edge_properties_map 7 | @directed = directed 8 | 9 | check_properties 10 | end 11 | 12 | def edge_property(u, v) 13 | if @directed 14 | property = @edge_properties_map[[u, v]] 15 | else 16 | property = @edge_properties_map[[u, v]] || @edge_properties_map[[v, u]] 17 | end 18 | 19 | validate_property(property, u, v) 20 | 21 | property 22 | end 23 | 24 | private 25 | 26 | def check_properties 27 | @edge_properties_map.each { |(u, v), property| validate_property(property, u, v) } if @edge_properties_map.respond_to?(:each) 28 | end 29 | 30 | def validate_property(property, u, v) 31 | report_missing_property(property, u, v) 32 | end 33 | 34 | def report_missing_property(property, u, v) 35 | raise ArgumentError.new("property of edge (#{u}, #{v}) is not defined") unless property 36 | end 37 | 38 | end # EdgePropertiesMap 39 | 40 | 41 | class NonNegativeEdgePropertiesMap < EdgePropertiesMap 42 | 43 | private 44 | 45 | def validate_property(property, u, v) 46 | super 47 | report_negative_property(property, u, v) 48 | end 49 | 50 | def report_negative_property(property, u, v) 51 | raise ArgumentError.new("property of edge (#{u}, #{v}) is negative") if property < 0 52 | end 53 | 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/rgl/edmonds_karp.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/edge_properties_map' 2 | require 'rgl/traversal' 3 | 4 | module RGL 5 | 6 | # Implements {https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm 7 | # Edmonds–Karp algorithm}. 8 | # @see Graph#maximum_flow 9 | class EdmondsKarpAlgorithm 10 | 11 | # Initializes Edmonds-Karp algorithm for a _graph_ with provided edges capacities map. 12 | # 13 | def initialize(graph, edge_capacities_map) 14 | raise NotDirectedError.new('Edmonds-Karp algorithm can only be applied to a directed graph') unless graph.directed? 15 | 16 | @graph = graph 17 | validate_edge_capacities(edge_capacities_map) 18 | @edge_capacities_map = NonNegativeEdgePropertiesMap.new(edge_capacities_map, true) 19 | end 20 | 21 | # Finds the maximum flow from the _source_ to the _sink_ in the graph. 22 | # 23 | # Returns flows map as a hash that maps each edge of the graph to a flow 24 | # through that edge that is required to reach the maximum total flow. 25 | # 26 | # @return [Hash] 27 | def maximum_flow(source, sink) 28 | raise ArgumentError.new("source and sink can't be equal") if source == sink 29 | 30 | @flow_map = Hash.new(0) 31 | @residual_capacity_map = lambda { |u, v| @edge_capacities_map.edge_property(u, v) - @flow_map[[u, v]] } 32 | 33 | loop do 34 | bfs = EdmondsKarpBFSIterator.new(@graph, source, sink, @residual_capacity_map) 35 | 36 | bfs.move_forward_until { bfs.color_map[sink] == :GRAY } 37 | 38 | if bfs.color_map[sink] == :WHITE 39 | break # no more augmenting paths 40 | else 41 | min_residual_capacity = INFINITY 42 | 43 | augmenting_path = [sink] 44 | 45 | while augmenting_path.first != source 46 | v = augmenting_path.first 47 | u = bfs.parents_map[v] 48 | 49 | augmenting_path.unshift(u) 50 | min_residual_capacity = [min_residual_capacity, @residual_capacity_map[u, v]].min 51 | end 52 | 53 | augmenting_path.each_cons(2) do |(uu, vv)| 54 | @flow_map[[uu, vv]] += min_residual_capacity 55 | @flow_map[[vv, uu]] -= min_residual_capacity 56 | end 57 | end 58 | end 59 | 60 | @flow_map 61 | end 62 | 63 | private 64 | 65 | def validate_edge_capacities(edge_capacities_map) 66 | @graph.each_edge do |u, v| 67 | raise ArgumentError.new("reverse edge for (#{u}, #{v}) is missing") unless @graph.has_edge?(v, u) 68 | validate_capacity(u, v, edge_capacities_map) 69 | end 70 | end 71 | 72 | def validate_capacity(u, v, edge_capacities_map) 73 | capacity = get_capacity(u, v, edge_capacities_map) 74 | reverse_capacity = get_capacity(v, u, edge_capacities_map) 75 | 76 | validate_negative_capacity(u, v, capacity) 77 | validate_negative_capacity(v, u, reverse_capacity) 78 | 79 | raise ArgumentError.new("either (#{u}, #{v}) or (#{v}, #{u}) should have 0 capacity") unless [capacity, reverse_capacity].include?(0) 80 | end 81 | 82 | def get_capacity(u, v, edge_capacities_map) 83 | edge_capacities_map.fetch([u, v]) { raise ArgumentError.new("capacity for edge (#{u}, #{v}) is missing") } 84 | end 85 | 86 | def validate_negative_capacity(u, v, capacity) 87 | raise ArgumentError.new("capacity of edge (#{u}, #{v}) is negative") unless capacity >= 0 88 | end 89 | 90 | class EdmondsKarpBFSIterator < BFSIterator 91 | 92 | attr_accessor :parents_map 93 | 94 | def initialize(graph, start, stop, residual_capacities) 95 | super(graph, start) 96 | @residual_capacities = residual_capacities 97 | @stop_vertex = stop 98 | end 99 | 100 | def reset 101 | super 102 | @parents_map = {} 103 | end 104 | 105 | def follow_edge?(u, v) 106 | # follow only edges with positive residual capacity 107 | super && @residual_capacities[u, v] > 0 108 | end 109 | 110 | def handle_tree_edge(u, v) 111 | super 112 | @parents_map[v] = u 113 | end 114 | 115 | end # class EdmondsKarpBFSIterator 116 | 117 | end # class EdmondsKarpAlgorithm 118 | 119 | 120 | module Graph 121 | 122 | # Finds the maximum flow from the _source_ to the _sink_ in the graph. 123 | # 124 | # Returns flows map as a hash that maps each edge of the graph to a flow through that edge that is required to reach 125 | # the maximum total flow. 126 | # 127 | # For the method to work, the graph should be first altered so that for each directed edge (u, v) it contains reverse 128 | # edge (u, v). Capacities of the primary edges should be non-negative, while reverse edges should have zero capacity. 129 | # 130 | # Raises ArgumentError if the graph is not directed. 131 | # 132 | # Raises ArgumentError if a reverse edge is missing, edge capacity is missing, an edge has negative capacity, or a 133 | # reverse edge has positive capacity. 134 | # 135 | # @return [Hash] 136 | def maximum_flow(edge_capacities_map, source, sink) 137 | EdmondsKarpAlgorithm.new(self, edge_capacities_map).maximum_flow(source, sink) 138 | end 139 | 140 | end # module Graph 141 | 142 | end 143 | -------------------------------------------------------------------------------- /lib/rgl/graph_iterator.rb: -------------------------------------------------------------------------------- 1 | require 'stream' 2 | 3 | require 'rgl/graph_wrapper' 4 | 5 | module RGL 6 | 7 | # A GraphIterator is the abstract basis for all Iterators on graphs. 8 | # Each graph iterator should implement the protocol defined in module 9 | # {https://rubydoc.info/github/monora/stream Stream}. 10 | # 11 | module GraphIterator 12 | include Stream 13 | include GraphWrapper 14 | 15 | # @return [int] 16 | def length 17 | inject(0) { |sum| sum + 1 } 18 | end 19 | end 20 | 21 | end # RGL 22 | -------------------------------------------------------------------------------- /lib/rgl/graph_visitor.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/graph_wrapper' 2 | 3 | module RGL 4 | 5 | # Module GraphVisitor defines the 6 | # {https://www.boost.org/libs/graph/doc/visitor_concepts.html BGL Visitor Concepts}. 7 | # 8 | # Visitors provide a mechanism for extending an algorithm (i.e., for 9 | # customizing what is done at each step of the algorithm). They allow users 10 | # to insert their own operations at various steps within a graph algorithm. 11 | # 12 | # Graph algorithms typically have multiple event points where one may want to 13 | # insert a call-back. Therefore, visitors have several methods that 14 | # correspond to the various event points. Each algorithm has a different 15 | # set of event points. The following are common to both DFS and BFS search: 16 | # 17 | # * examine_vertex 18 | # * examine_edge 19 | # * tree_edge 20 | # * back_edge 21 | # * forward_edge 22 | # * finish_vertex 23 | # 24 | # These methods are all named +handle_*+ and can be set to appropriate blocks, 25 | # using the methods +set_*_event_handler+, which are defined for each event 26 | # mentioned above. 27 | # 28 | # As an alternative, you can also override the +handle_*+ methods in a 29 | # subclass, to configure the algorithm (as an example, see TarjanSccVisitor). 30 | # 31 | # During a graph traversal, vertices are *colored* using the colors :GRAY 32 | # (when waiting) and :BLACK when finished. All other vertices are :WHITE. 33 | # The +color_map+ is also maintained in the visitor. 34 | # 35 | module GraphVisitor 36 | 37 | include GraphWrapper 38 | 39 | # @return [Hash] a map which store the colors for each vertex 40 | attr_reader :color_map 41 | 42 | # Create a new GraphVisitor on _graph_. 43 | # @param [Graph] graph 44 | def initialize(graph) 45 | super(graph) 46 | reset 47 | end 48 | 49 | # Mark each vertex unvisited (i.e. :WHITE) 50 | # 51 | def reset 52 | @color_map = Hash.new(:WHITE) 53 | end 54 | 55 | # Returns true if vertex _v_ is colored :BLACK (i.e. finished). 56 | # 57 | def finished_vertex?(v) 58 | @color_map[v] == :BLACK 59 | end 60 | 61 | # Shall we follow the edge (u,v); i.e. v has color :WHITE 62 | # 63 | def follow_edge?(u, v) 64 | @color_map[v] == :WHITE 65 | end 66 | 67 | # Attach a map to the visitor which records the distance of a visited 68 | # vertex to the start vertex. 69 | # 70 | # This is similar to BGLs 71 | # {https://www.boost.org/libs/graph/doc/distance_recorder.html distance_recorder}. 72 | # 73 | # After the +distance_map+ is attached, the visitor has a new method 74 | # +distance_to_root+, which answers the distance to the start vertex. 75 | # 76 | def attach_distance_map(map = Hash.new(0)) 77 | @distance_map = map 78 | 79 | # add distance map support to the current visitor instance 80 | extend(DistanceMapSupport) 81 | end 82 | 83 | module DistanceMapSupport 84 | 85 | def handle_tree_edge(u, v) 86 | super 87 | @distance_map[v] = @distance_map[u] + 1 88 | end 89 | 90 | # Answer the distance to the start vertex. 91 | 92 | def distance_to_root(v) 93 | @distance_map[v] 94 | end 95 | 96 | end # module DistanceMapSupport 97 | 98 | module ClassMethods 99 | 100 | # Defines an event handler. 101 | # 102 | def def_event_handlers(*events) 103 | events.each do |event| 104 | params = event.to_s.include?('edge') ? 'u, v' : 'u' 105 | 106 | handler = "@#{event}_event_handler" 107 | 108 | class_eval <<-END 109 | def handle_#{event}(#{params}) 110 | #{handler}.call(#{params}) if defined? #{handler} 111 | end 112 | 113 | def set_#{event}_event_handler(&block) 114 | #{handler} = block 115 | end 116 | END 117 | end 118 | end 119 | 120 | alias def_event_handler def_event_handlers 121 | 122 | end # module ClassMethods 123 | 124 | extend ClassMethods # add class methods to GraphVisitor class itself 125 | 126 | def self.included(base) 127 | # when GraphVisitor is included into a class/module, add class methods as well 128 | base.extend ClassMethods 129 | end 130 | 131 | def_event_handlers :examine_vertex, 132 | :examine_edge, 133 | :tree_edge, 134 | :back_edge, 135 | :forward_edge, 136 | :finish_vertex 137 | 138 | end # module GraphVisitor 139 | 140 | end # module RGL 141 | -------------------------------------------------------------------------------- /lib/rgl/graph_wrapper.rb: -------------------------------------------------------------------------------- 1 | module RGL 2 | 3 | module GraphWrapper 4 | 5 | # @return [Graph] the wrapped graph 6 | attr_accessor :graph 7 | 8 | # Creates a new GraphWrapper on _graph_. 9 | # 10 | def initialize(graph) 11 | @graph = graph 12 | end 13 | 14 | end # module GraphWrapper 15 | 16 | end # RGL 17 | -------------------------------------------------------------------------------- /lib/rgl/graphxml.rb: -------------------------------------------------------------------------------- 1 | # graphxml.rb 2 | # 3 | # This file contains minimal support for creating RGL graphs from the GraphML 4 | # format (see https://graphml.graphdrawing.org/). The main purpose is to 5 | # have a rich set of example graphs to have some more tests. 6 | # 7 | # See the examples directory, which contains a subdirectory _north_ with the 8 | # Graph catalog GraphViz (see 9 | # https://www.research.att.com/sw/tools/graphviz/refs.html). 10 | # 11 | # We use REXML::StreamListener from the REXML library 12 | # (https://www.germane-software.com/software/rexml) to parse the grapml files. 13 | 14 | require 'rgl/mutable' 15 | require 'rexml/document' 16 | require 'rexml/streamlistener' 17 | 18 | module RGL 19 | 20 | module MutableGraph 21 | 22 | # Used to parse a subset of GraphML into an RGL graph implementation. 23 | # 24 | class MutableGraphParser 25 | 26 | include REXML::StreamListener 27 | 28 | # First resets +graph+ to be empty and stores a reference for use with 29 | # #tag_start. 30 | # 31 | def initialize(graph) 32 | @graph = graph 33 | @graph.remove_vertices(@graph.vertices) 34 | end 35 | 36 | # Processes incoming edge and node elements from GraphML in order to 37 | # populate the graph given to #new. 38 | # 39 | def tag_start(name, attrs) 40 | case name 41 | when 'edge' 42 | @graph.add_edge(attrs['source'], attrs['target']) 43 | when 'node' 44 | @graph.add_vertex(attrs['id']) 45 | end 46 | end 47 | 48 | end # class MutableGraphParser 49 | 50 | # Initializes an RGL graph from a subset of the GraphML format given in 51 | # +source+ (see https://graphml.graphdrawing.org/). 52 | # 53 | def from_graphxml(source) 54 | listener = MutableGraphParser.new(self) 55 | REXML::Document.parse_stream(source, listener) 56 | self 57 | end 58 | 59 | end # module MutableGraph 60 | 61 | end # module RGL 62 | -------------------------------------------------------------------------------- /lib/rgl/implicit.rb: -------------------------------------------------------------------------------- 1 | # implicit.rb 2 | # 3 | # This file contains the definition of the class RGL::ImplicitGraph, which 4 | # defines vertex and edge iterators using blocks (which again call blocks). 5 | # 6 | 7 | require 'rgl/base' 8 | 9 | module RGL 10 | 11 | # An ImplicitGraph provides a handy way to define graphs on the fly, using two 12 | # blocks for the two iterators defining a graph. Other examples are given by the 13 | # methods {#vertices_filtered_by} and {#edges_filtered_by}, which can be 14 | # applied to any graph. 15 | # 16 | # @example 17 | # # A directed cyclic graph, with five vertices can be created as follows: 18 | # g = RGL::ImplicitGraph.new do |g| 19 | # g.vertex_iterator { |b| 0.upto(4,&b) } 20 | # g.adjacent_iterator { |x, b| b.call((x+1)%5) } 21 | # g.directed = true 22 | # end 23 | # 24 | # g.to_s # => "(0-1)(1-2)(2-3)(3-4)(4-0)" 25 | # 26 | class ImplicitGraph 27 | 28 | include Graph 29 | 30 | attr_writer :directed 31 | 32 | EMPTY_VERTEX_ITERATOR = proc { |b| } 33 | EMPTY_NEIGHBOR_ITERATOR = proc { |x, b| } 34 | 35 | # Create a new {ImplicitGraph}, which is empty by default. The caller should 36 | # configure the graph using vertex and neighbor iterators. If the graph is 37 | # directed, the client should set +@directed+ to true. The default value 38 | # for +@directed+ is false. 39 | # 40 | def initialize 41 | @directed = false 42 | @vertex_iterator = EMPTY_VERTEX_ITERATOR 43 | @adjacent_iterator = EMPTY_NEIGHBOR_ITERATOR 44 | yield self if block_given? # Let client overwrite defaults. 45 | end 46 | 47 | # Returns the value of +@directed+. 48 | # 49 | def directed? 50 | @directed 51 | end 52 | 53 | def each_vertex(&block) 54 | @vertex_iterator.call(block) 55 | end 56 | 57 | def each_adjacent(v, &block) 58 | @adjacent_iterator.call(v, block) 59 | end 60 | 61 | def each_edge(&block) 62 | if defined? @edge_iterator 63 | @edge_iterator.call(block) 64 | else 65 | super # use default implementation 66 | end 67 | end 68 | 69 | # Sets the vertex_iterator to _block_, 70 | # which must be a block of one parameter 71 | # which again is the block called by {#each_vertex}. 72 | # 73 | def vertex_iterator(&block) 74 | @vertex_iterator = block 75 | end 76 | 77 | # Sets the adjacent_iterator to _block_, 78 | # which must be a block of two parameters: 79 | # 80 | # The first parameter is the vertex the neighbors of which are to be 81 | # traversed. 82 | # 83 | # The second is the block which will be called for each neighbor 84 | # of this vertex. 85 | # 86 | def adjacent_iterator(&block) 87 | @adjacent_iterator = block 88 | end 89 | 90 | # Sets the edge_iterator to _block_, which must be a block of two 91 | # parameters: The first parameter is the source of the edges; the 92 | # second is the target of the edge. 93 | # 94 | def edge_iterator(&block) 95 | @edge_iterator = block 96 | end 97 | 98 | end # class ImplicitGraph 99 | 100 | 101 | module Graph 102 | 103 | # Returns a new {ImplicitGraph} which has as vertices all vertices of the 104 | # receiver which satisfy the predicate _filter_. 105 | # 106 | # The methods provides similar functionality as 107 | # {https://www.boost.org/doc/libs/1_36_0/libs/graph/doc/filtered_graph.html 108 | # BGLs Filtered Graph} 109 | # 110 | # @example 111 | # 112 | # def complete (n) 113 | # set = n.integer? ? (1..n) : n 114 | # RGL::ImplicitGraph.new do |g| 115 | # g.vertex_iterator { |b| set.each(&b) } 116 | # g.adjacent_iterator do |x, b| 117 | # set.each { |y| b.call(y) unless x == y } 118 | # end 119 | # end 120 | # end 121 | # 122 | # complete(4).to_s # => "(1=2)(1=3)(1=4)(2=3)(2=4)(3=4)" 123 | # complete(4).vertices_filtered_by { |v| v != 4 }.to_s # => "(1=2)(1=3)(2=3)" 124 | # @return [ImplicitGraph] 125 | def vertices_filtered_by(&filter) 126 | implicit_graph do |g| 127 | g.vertex_iterator do |b| 128 | self.each_vertex { |v| b.call(v) if filter.call(v) } 129 | end 130 | 131 | g.adjacent_iterator do |v, b| 132 | self.each_adjacent(v) { |u| b.call(u) if filter.call(u) } 133 | end 134 | end 135 | end 136 | 137 | # Returns a new {ImplicitGraph} which has as edges all edges of the receiver 138 | # which satisfy the predicate _filter_ (a block with two parameters). 139 | # 140 | # @example 141 | # 142 | # g = complete(7).edges_filtered_by { |u,v| u+v == 7 } 143 | # g.to_s => "(1=6)(2=5)(3=4)" 144 | # g.vertices => [1, 2, 3, 4, 5, 6, 7] 145 | # @return [ImplicitGraph] 146 | def edges_filtered_by(&filter) 147 | implicit_graph do |g| 148 | g.adjacent_iterator do |v, b| 149 | self.each_adjacent(v) do |u| 150 | b.call(u) if filter.call(v, u) 151 | end 152 | end 153 | 154 | g.edge_iterator do |b| 155 | self.each_edge { |u, v| b.call(u, v) if filter.call(u, v) } 156 | end 157 | end 158 | end 159 | 160 | # Return a new {ImplicitGraph} which is isomorphic (i.e. has same edges and 161 | # vertices) to the receiver. It is a shortcut, also used by 162 | # {#edges_filtered_by} and {#vertices_filtered_by}. 163 | # @return [ImplicitGraph] 164 | def implicit_graph 165 | result = ImplicitGraph.new do |g| 166 | g.vertex_iterator { |b| self.each_vertex(&b) } 167 | g.adjacent_iterator { |v, b| self.each_adjacent(v, &b) } 168 | g.directed = self.directed? 169 | end 170 | 171 | yield result if block_given? # let client overwrite defaults 172 | result 173 | end 174 | 175 | end # module Graph 176 | 177 | end # module RGL 178 | -------------------------------------------------------------------------------- /lib/rgl/mutable.rb: -------------------------------------------------------------------------------- 1 | # mutable.rb 2 | 3 | require 'rgl/base' 4 | 5 | module RGL 6 | 7 | # A MutableGraph can be changed via the addition or removal of edges and 8 | # vertices. 9 | # 10 | module MutableGraph 11 | 12 | include Graph 13 | 14 | # Add a new vertex _v_ to the graph. If the vertex is already in the 15 | # graph (tested via eql?), the method does nothing. 16 | # 17 | def add_vertex(v) 18 | raise NotImplementedError 19 | end 20 | 21 | # Inserts the edge (u,v) into the graph. 22 | # 23 | # Note that for undirected graphs, (u,v) is the same edge as (v,u), so 24 | # after a call to the function #add_edge, this implies that edge (u,v) 25 | # will appear in the out-edges of u and (u,v) (or equivalently (v,u)) 26 | # will appear in the out-edges of v. Put another way, v will be adjacent 27 | # to u and u will be adjacent to v. 28 | # 29 | def add_edge(u, v) 30 | raise NotImplementedError 31 | end 32 | 33 | # Add all objects in _a_ to the vertex set. 34 | # 35 | def add_vertices(*a) 36 | a.each { |v| add_vertex v } 37 | end 38 | 39 | # Add all edges in the _edges_ array to the edge set. Elements of the 40 | # array can be both two-element arrays or instances of {Edge::DirectedEdge} or 41 | # {Edge::UnDirectedEdge}. 42 | # 43 | def add_edges(*edges) 44 | edges.each { |edge| add_edge(edge[0], edge[1]) } 45 | end 46 | 47 | # Remove u from the vertex set of the graph. All edges whose target is 48 | # _v_ are also removed from the edge set of the graph. 49 | # 50 | # Postcondition: num_vertices is one less, _v_ no longer appears in the 51 | # vertex set of the graph, and there no edge with source or target _v_. 52 | # 53 | def remove_vertex(v) 54 | raise NotImplementedError 55 | end 56 | 57 | # Remove the edge (u,v) from the graph. If the graph allows parallel 58 | # edges, this removes all occurrences of (u,v). 59 | # 60 | # Precondition: u and v are vertices in the graph. 61 | # Postcondition: (u,v) is no longer in the edge set for g. 62 | # 63 | def remove_edge(u, v) 64 | raise NotImplementedError 65 | end 66 | 67 | # Remove all vertices specified by the array a from the graph by calling 68 | # {#remove_vertex}. 69 | # 70 | def remove_vertices(*a) 71 | a.each { |v| remove_vertex v } 72 | end 73 | 74 | # Returns all minimum cycles that pass through a give vertex. 75 | # The format is an Array of cycles, with each cycle being an Array 76 | # of vertices in the cycle. 77 | # @return [Array[Array]] 78 | def cycles_with_vertex(vertex) 79 | cycles_with_vertex_helper(vertex, vertex, []) 80 | end 81 | 82 | protected 83 | 84 | def cycles_with_vertex_helper(vertex, start, visited) 85 | adjacent_vertices(start).reject { |x| visited.include?(x) }.inject([]) do |acc, adj| 86 | local_visited = Array.new(visited) << adj 87 | acc << local_visited if (adj==vertex) 88 | acc = acc + cycles_with_vertex_helper(vertex, adj, local_visited) 89 | end 90 | end 91 | 92 | public 93 | 94 | # @return [Array] of all minimum cycles in a graph 95 | # 96 | # This is not an efficient implementation O(n^4) and could 97 | # be done using Minimum Spanning Trees. Hint. Hint. 98 | # 99 | def cycles 100 | g = self.clone 101 | self.inject([]) do |acc, v| 102 | acc = acc.concat(g.cycles_with_vertex(v)) 103 | g.remove_vertex(v); acc 104 | end 105 | end 106 | 107 | end # module MutableGraph 108 | 109 | end # module RGL 110 | -------------------------------------------------------------------------------- /lib/rgl/path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rgl/traversal' 4 | 5 | module RGL 6 | module Graph 7 | # Checks whether a path exists between _source_ and _target_ vertices 8 | # in the graph. 9 | # 10 | def path?(source, target) 11 | return false unless has_vertex?(source) 12 | 13 | bfs_iterator = bfs_iterator(source) 14 | bfs_iterator.include?(target) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rgl/path_builder.rb: -------------------------------------------------------------------------------- 1 | module RGL 2 | 3 | class PathBuilder 4 | 5 | def initialize(source, parents_map) 6 | @source = source 7 | @parents_map = parents_map 8 | @paths = {} 9 | end 10 | 11 | def path(target) 12 | if @paths.has_key?(target) 13 | @paths[target] 14 | else 15 | @paths[target] = restore_path(target) 16 | end 17 | end 18 | 19 | # @return [Hash] 20 | def paths(targets) 21 | paths_map = {} 22 | 23 | targets.each do |target| 24 | paths_map[target] = path(target) 25 | end 26 | 27 | paths_map 28 | end 29 | 30 | private 31 | 32 | def restore_path(target) 33 | return [@source] if target == @source 34 | 35 | parent = @parents_map[target] 36 | path(parent) + [target] if parent 37 | end 38 | 39 | end 40 | 41 | end # RGL 42 | -------------------------------------------------------------------------------- /lib/rgl/prim.rb: -------------------------------------------------------------------------------- 1 | require 'rgl/dijkstra' 2 | require 'rgl/adjacency' 3 | 4 | module RGL 5 | 6 | # Implements {https://en.wikipedia.org/wiki/Prim%27s_algorithm Prim's algorithm}. 7 | # @see Graph#prim_minimum_spanning_tree 8 | class PrimAlgorithm 9 | 10 | # Replacement for default distance combinator that is used in Dijkstra's algorithm. While building a minimum 11 | # spanning tree (MST) we're interested not in the distance from the source (the vertex that is added first to the 12 | # MST) to a vertex, but rather in the distance between already completed part of the MST (that includes all examined 13 | # vertices) and the vertex. Therefore, when we examine an edge (u, v), where _u_ is already in the MST and _v_ is 14 | # not, the distance from the MST to the vertex _v_ is the weight of the edge (u, v). 15 | DISTANCE_COMBINATOR = lambda { |_, edge_weight| edge_weight } 16 | 17 | # Initializes Prim's algorithm for a _graph_ with provided edges weights map. 18 | # 19 | def initialize(graph, edge_weights_map, visitor) 20 | @graph = graph 21 | @edge_weights_map = EdgePropertiesMap.new(edge_weights_map, @graph.directed?) 22 | @visitor = visitor 23 | @dijkstra = DijkstraAlgorithm.new(@graph, @edge_weights_map, @visitor, DISTANCE_COMBINATOR) 24 | end 25 | 26 | # Returns minimum spanning tree for the _graph_. If the graph is disconnected, Prim's algorithm will find the minimum 27 | # spanning tree only for one of the connectivity components. If _start_vertex_ is given, Dijkstra's search will be 28 | # started in this vertex and the algorithm will return the minimum spanning tree for the component it belongs to. 29 | # 30 | def minimum_spanning_tree(start_vertex = nil) 31 | @dijkstra.find_shortest_paths(start_vertex || @graph.vertices.first) 32 | AdjacencyGraph[*@visitor.parents_map.to_a.flatten] 33 | end 34 | 35 | end # class PrimAlgorithm 36 | 37 | 38 | module Graph 39 | 40 | # Finds the minimum spanning tree of the graph. 41 | # 42 | # Returns an {AdjacencyGraph} that represents the minimum spanning tree of the graph's connectivity component that 43 | # contains the starting vertex. The algorithm starts from an arbitrary vertex if the _start_vertex_ is not given. 44 | # Since the implementation relies on the Dijkstra's algorithm, Prim's algorithm uses the same visitor class and emits 45 | # the same events. 46 | # 47 | # Raises ArgumentError if edge weight is undefined. 48 | # 49 | def prim_minimum_spanning_tree(edge_weights_map, start_vertex = nil, visitor = DijkstraVisitor.new(self)) 50 | PrimAlgorithm.new(self, edge_weights_map, visitor).minimum_spanning_tree(start_vertex) 51 | end 52 | 53 | end # module Graph 54 | 55 | end # module RGL 56 | -------------------------------------------------------------------------------- /lib/rgl/topsort.rb: -------------------------------------------------------------------------------- 1 | # topsort.rb 2 | 3 | require 'rgl/graph_iterator' 4 | 5 | module RGL 6 | 7 | # Topological Sort Iterator 8 | # 9 | # The topological sort algorithm creates a linear ordering of the vertices 10 | # such that if edge (u,v) appears in the graph, then u comes before v in 11 | # the ordering. The graph must be a directed acyclic graph. 12 | # 13 | # The iterator can also be applied to an undirected graph or to a directed graph 14 | # which contains a cycle. In this case, the iterator does not reach all 15 | # vertices. The implementation of {Graph#acyclic?} uses this fact. 16 | # 17 | # @see Graph#topsort_iterator 18 | class TopsortIterator 19 | 20 | include GraphIterator 21 | 22 | def initialize(g) 23 | super(g) 24 | set_to_begin 25 | end 26 | 27 | def set_to_begin 28 | @waiting = Array.new 29 | @inDegrees = Hash.new(0) 30 | 31 | graph.each_vertex do |u| 32 | @inDegrees[u] = 0 unless @inDegrees.has_key?(u) 33 | graph.each_adjacent(u) do |v| 34 | @inDegrees[v] += 1 35 | end 36 | end 37 | 38 | @inDegrees.each_pair do |v, indegree| 39 | @waiting.push(v) if indegree.zero? 40 | end 41 | end 42 | 43 | # @private 44 | def basic_forward 45 | u = @waiting.pop 46 | graph.each_adjacent(u) do |v| 47 | @inDegrees[v] -= 1 48 | @waiting.push(v) if @inDegrees[v].zero? 49 | end 50 | u 51 | end 52 | 53 | def at_beginning? 54 | true 55 | end 56 | 57 | def at_end? 58 | @waiting.empty? 59 | end 60 | 61 | end # class TopsortIterator 62 | 63 | 64 | module Graph 65 | 66 | # @return [TopsortIterator] for the graph. 67 | # 68 | def topsort_iterator 69 | TopsortIterator.new(self) 70 | end 71 | 72 | # Returns true if the graph contains no cycles. This is only meaningful 73 | # for directed graphs. Returns false for undirected graphs. 74 | # 75 | def acyclic? 76 | topsort_iterator.length == num_vertices 77 | end 78 | 79 | end # module Graph 80 | 81 | end # module RGL 82 | -------------------------------------------------------------------------------- /lib/rgl/transitiv_closure.rb: -------------------------------------------------------------------------------- 1 | # This file is deprecated and only provided for backward compatibility. 2 | require 'rgl/transitivity' 3 | -------------------------------------------------------------------------------- /lib/rgl/version.rb: -------------------------------------------------------------------------------- 1 | module RGL 2 | VERSION = "0.6.6".freeze 3 | end 4 | -------------------------------------------------------------------------------- /rakelib/dep_graph.rake: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | begin 4 | require 'rgl/dot' 5 | require 'rgl/implicit' 6 | rescue Exception 7 | nil 8 | end 9 | 10 | # Example usage: 11 | # 12 | # rake -R/home/hd/src/rgl/rakelib -f /usr/lib/ruby/gems/1.8/gems/rails-1.0.0/Rakefile dep_graph 13 | desc "Generate dependency graph of rake tasks" 14 | task :dep_graph do |task| 15 | this_task = task.name 16 | 17 | dep = RGL::ImplicitGraph.new do |g| 18 | # vertices of the graph are all defined tasks without this task 19 | g.vertex_iterator do |b| 20 | Rake::Task.tasks.each do |t| 21 | b.call(t) unless t.name == this_task 22 | end 23 | end 24 | # neighbors of task t are its prerequisites 25 | g.adjacent_iterator { |t, b| t.prerequisites.each(&b) } 26 | g.directed = true 27 | end 28 | 29 | dep.write_to_graphic_file('png', this_task) 30 | puts "Wrote dependency graph to #{this_task}.png." 31 | end 32 | -------------------------------------------------------------------------------- /rgl.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), 'lib') 2 | require 'rgl/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'rgl' 6 | s.version = RGL::VERSION 7 | s.summary = "Ruby Graph Library" 8 | s.description = "RGL is a framework for graph data structures and algorithms" 9 | s.licenses = ['Ruby'] 10 | 11 | #### Dependencies and requirements. 12 | 13 | s.add_dependency 'stream', '~> 0.5.3' 14 | s.add_dependency 'pairing_heap', '>= 0.3', '< 4.0' 15 | s.add_dependency 'rexml', '~> 3.2', '>= 3.2.4' 16 | 17 | s.add_development_dependency 'rake', '~> 13.0' 18 | s.add_development_dependency 'yard', '~> 0.9' 19 | s.add_development_dependency 'test-unit', '~> 3.5' 20 | 21 | #### Which files are to be included in this gem? 22 | 23 | s.files = Dir[ 24 | 'lib/**/*.rb', 25 | 'CHANGELOG.md', 26 | 'examples/**/*', 27 | 'Gemfile', 28 | 'README.md', 29 | 'Rakefile', 30 | 'rakelib/*.rake', 31 | 'test/**/*.rb', 32 | ] 33 | 34 | #### Load-time details: library and application (you will need one or both). 35 | 36 | s.require_path = 'lib' 37 | 38 | #### Documentation and testing. 39 | 40 | s.extra_rdoc_files = ['README.md'] 41 | s.rdoc_options += [ 42 | '--title', 'RGL - Ruby Graph Library', 43 | '--main', 'README.md', 44 | '--line-numbers' 45 | ] 46 | 47 | #### Author and project details. 48 | 49 | s.authors = [ 50 | "Horst Duchene", 51 | "Kirill Lashuk" 52 | ] 53 | 54 | s.email = "monora@gmail.com" 55 | s.homepage = "https://github.com/monora/rgl" 56 | end 57 | -------------------------------------------------------------------------------- /script/test_all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ideas taken from https://github.com/rspec/rspec-core/blob/635baca47c63ce8b18e8b67e304c3980630543fd/script/test_all. 4 | 5 | function print_and_run { 6 | echo $1 7 | ($1) 8 | } 9 | 10 | function print_separator { 11 | echo 12 | echo "====================================================================" 13 | echo 14 | } 15 | 16 | set -e 17 | 18 | # Run all tests at once 19 | echo "Running all tests..." 20 | print_and_run "rake test" 21 | 22 | # Run tests one by one 23 | for file in `find test -iname '*_test.rb'`; do 24 | print_separator 25 | echo "Running tests from $file" 26 | print_and_run "ruby -Ilib:test $file" 27 | done 28 | 29 | -------------------------------------------------------------------------------- /test/bidirectional_graph_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/bidirectional_adjacency' 4 | require 'directed_graph_test' 5 | 6 | include RGL 7 | include RGL::Edge 8 | 9 | class TestBidirectionalAdjacencyGraph < Test::Unit::TestCase 10 | def setup 11 | @edges = [[1, 2], [1, 3], [2, 3], [2, 4], [2, 5], [2, 6], [3, 2], [3, 7], [3, 8], 12 | [5, 10], [6, 9], [7, 9], [7, 10], [8, 10]] 13 | @out_neighbors = Hash.new { |h, k| h[k] = Set.new } 14 | @in_neighbors = Hash.new { |h, k| h[k] = Set.new } 15 | @edges.each do |e| 16 | @out_neighbors[e[0]] << e[1] 17 | @in_neighbors[e[1]] << e[0] 18 | end 19 | @dg = BidirectionalAdjacencyGraph.new 20 | @edges.each do |(src, target)| 21 | @dg.add_edge(src, target) 22 | end 23 | @eg = BidirectionalAdjacencyGraph.new 24 | @gfa = BidirectionalAdjacencyGraph[1, 2, 3, 4] 25 | end 26 | 27 | def test_empty_graph 28 | assert @eg.empty? 29 | assert @eg.directed? 30 | assert(!@eg.has_edge?(2, 1)) 31 | assert(!@eg.has_out_edge?(2, 1)) 32 | assert(!@eg.has_in_edge?(1, 2)) 33 | assert(!@eg.has_vertex?(3)) 34 | # Non existent vertex result in a Name Error because each_key is 35 | # called for nil 36 | assert_raises(NoVertexError) { @eg.out_degree(3) } 37 | assert_raises(NoVertexError) { @eg.in_degree(3) } 38 | assert_equal([], @eg.vertices) 39 | assert_equal(0, @eg.size) 40 | assert_equal(0, @eg.num_vertices) 41 | assert_equal(0, @eg.num_edges) 42 | assert_equal(DirectedEdge, @eg.edge_class) 43 | assert_empty(@eg.edges) 44 | end 45 | 46 | def test_add 47 | @eg.add_edge(1, 2) 48 | assert(!@eg.empty?) 49 | assert(@eg.has_edge?(1, 2)) 50 | assert(@eg.has_out_edge?(1, 2)) 51 | assert(@eg.has_in_edge?(2, 1)) 52 | assert(!@eg.has_edge?(2, 1)) 53 | assert(!@eg.has_out_edge?(2, 1)) 54 | assert(!@eg.has_in_edge?(1, 2)) 55 | assert(@eg.has_vertex?(1) && @eg.has_vertex?(2)) 56 | assert(!@eg.has_vertex?(3)) 57 | 58 | assert_equal([1, 2], @eg.vertices.sort) 59 | assert([DirectedEdge.new(1, 2)].eql?(@eg.edges)) 60 | assert_equal("(1-2)", @eg.edges.join) 61 | 62 | assert_equal([2], @eg.adjacent_vertices(1)) 63 | assert_equal([2], @eg.out_neighbors(1)) 64 | assert_equal([], @eg.in_neighbors(1)) 65 | assert_equal([], @eg.adjacent_vertices(2)) 66 | assert_equal([], @eg.out_neighbors(2)) 67 | assert_equal([1], @eg.in_neighbors(2)) 68 | 69 | assert_equal(1, @eg.out_degree(1)) 70 | assert_equal(0, @eg.in_degree(1)) 71 | assert_equal(0, @eg.out_degree(2)) 72 | assert_equal(1, @eg.in_degree(2)) 73 | end 74 | 75 | def test_edges 76 | assert_equal(14, @dg.edges.length) 77 | assert_equal(@edges.map { |e| e[0] }.to_set, @dg.edges.map { |l| l.source }.to_set) 78 | assert_equal(@edges.map { |e| e[1] }.to_set, @dg.edges.map { |l| l.target }.to_set) 79 | assert_equal("(1-2)(1-3)(2-3)(2-4)(2-5)(2-6)(3-2)(3-7)(3-8)(5-10)(6-9)(7-10)(7-9)(8-10)", @dg.edges.map { |l| l.to_s }.sort.join) 80 | end 81 | 82 | def test_vertices 83 | assert_equal(@edges.flatten.to_set, @dg.vertices.to_set) 84 | end 85 | 86 | def test_edges_from_to? 87 | @edges.each do |u, v| 88 | assert @dg.has_edge?(u, v) 89 | assert @dg.has_out_edge?(u, v) 90 | assert @dg.has_in_edge?(v, u) 91 | end 92 | end 93 | 94 | def test_remove_edges 95 | @dg.remove_edge 1, 2 96 | assert !@dg.has_edge?(1, 2) 97 | assert !@dg.has_out_edge?(1, 2) 98 | assert !@dg.has_in_edge?(2, 1) 99 | @dg.remove_edge 1, 2 100 | assert !@dg.has_edge?(1, 2) 101 | assert !@dg.has_out_edge?(1, 2) 102 | assert !@dg.has_in_edge?(2, 1) 103 | @dg.remove_vertex 3 104 | assert !@dg.has_vertex?(3) 105 | assert !@dg.has_edge?(2, 3) 106 | assert !@dg.has_out_edge?(2, 3) 107 | assert !@dg.has_in_edge?(3, 2) 108 | assert_equal('(2-4)(2-5)(2-6)(5-10)(6-9)(7-9)(7-10)(8-10)', @dg.edges.join) 109 | end 110 | 111 | def test_add_vertices 112 | @eg.add_vertices 1, 3, 2, 4 113 | assert_equal @eg.vertices.sort, [1, 2, 3, 4] 114 | 115 | @eg.remove_vertices 1, 3 116 | assert_equal @eg.vertices.sort, [2, 4] 117 | end 118 | 119 | def test_creating_from_array 120 | assert_equal([1, 2, 3, 4], @gfa.vertices.sort) 121 | assert_equal('(1-2)(3-4)', @gfa.edges.join) 122 | end 123 | 124 | def test_creating_from_graphs 125 | dg2 = BidirectionalAdjacencyGraph.new(Set, @dg, @gfa) 126 | assert_equal(dg2.vertices.to_set, (@dg.vertices + @gfa.vertices).to_set) 127 | assert_equal(dg2.edges.to_set, (@dg.edges + @gfa.edges).to_set) 128 | end 129 | 130 | def test_reverse 131 | # Add isolated vertex 132 | @dg.add_vertex(42) 133 | reverted = @dg.reverse 134 | 135 | @dg.each_edge do |u, v| 136 | assert(reverted.has_edge?(v, u)) 137 | end 138 | 139 | assert(reverted.has_vertex?(42), 'Reverted graph should contain isolated Vertex 42') 140 | end 141 | 142 | def test_to_undirected 143 | undirected = @dg.to_undirected 144 | assert_equal '(1=2)(1=3)(2=3)(2=4)(2=5)(2=6)(3=7)(3=8)(5=10)(6=9)(7=9)(7=10)(8=10)', undirected.edges.sort.join 145 | end 146 | 147 | def test_neighbors 148 | @edges.flatten.to_set.each do |v| 149 | assert_equal @out_neighbors[v], @dg.out_neighbors(v).to_set 150 | assert_equal @in_neighbors[v], @dg.in_neighbors(v).to_set 151 | end 152 | @dg.add_vertex(42) 153 | assert_empty(@dg.out_neighbors(42)) 154 | assert_empty(@dg.in_neighbors(42)) 155 | end 156 | 157 | def test_each_neighbor 158 | @edges.flatten.to_set.each do |v| 159 | out_neighbors = Set.new 160 | @dg.each_out_neighbor(v) { |n| out_neighbors << n } 161 | assert_equal @out_neighbors[v], out_neighbors 162 | in_neighbors = Set.new 163 | @dg.each_in_neighbor(v) { |n| in_neighbors << n } 164 | assert_equal @in_neighbors[v], in_neighbors 165 | end 166 | @dg.add_vertex(42) 167 | out_neighbors = Set.new 168 | @dg.each_out_neighbor(42) { |n| out_neighbors << n } 169 | assert_empty(out_neighbors) 170 | in_neighbors = Set.new 171 | @dg.each_in_neighbor(42) { |n| in_neighbors << n } 172 | assert_empty(in_neighbors) 173 | end 174 | 175 | def test_degrees 176 | @edges.flatten.to_set.each do |v| 177 | assert_equal @out_neighbors[v].size, @dg.out_degree(v) 178 | assert_equal @in_neighbors[v].size, @dg.in_degree(v) 179 | assert_equal @out_neighbors[v].size + @in_neighbors[v].size, @dg.degree(v) 180 | end 181 | end 182 | 183 | end 184 | -------------------------------------------------------------------------------- /test/bipartite_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/bipartite' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | class TestBipartite < Test::Unit::TestCase 9 | 10 | def test_bipartite_sets 11 | assert_equal([[1, 2, 3], [4, 5, 6]], bipartite_sets(AdjacencyGraph[1,5, 1,6, 2,4, 2,5, 3,4, 3,5, 3,6])) 12 | end 13 | 14 | def test_bipartite_sets_for_non_bipartite_graph 15 | assert_equal(nil, bipartite_sets(AdjacencyGraph[1,4, 1,5, 2,3, 2,4, 2,5, 3,5])) 16 | end 17 | 18 | def test_bipartite_sets_for_directed_graph 19 | assert_raise(NotUndirectedError, 'bipartite sets can only be found for an undirected graph') do 20 | DirectedAdjacencyGraph.new.bipartite_sets 21 | end 22 | end 23 | 24 | def test_bipartite_sets_for_bipartite_disconnected_graph 25 | assert_equal([[1, 3], [2, 4]], bipartite_sets(AdjacencyGraph[1,2, 3,4])) 26 | end 27 | 28 | def test_bipartite_sets_for_non_bipartite_disconnected_graph 29 | assert_equal(nil, bipartite_sets(AdjacencyGraph[1,2, 3,4, 4,5, 5,3])) 30 | end 31 | 32 | def test_bipartite 33 | assert(AdjacencyGraph[1,2, 2,3].bipartite?) 34 | end 35 | 36 | def test_not_bipartite 37 | assert(!AdjacencyGraph[1,2, 2,3, 3,1].bipartite?) 38 | end 39 | 40 | private 41 | 42 | def bipartite_sets(graph) 43 | sets = graph.bipartite_sets 44 | sets && sets.map { |set| set.sort }.sort 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /test/components_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/connected_components' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | def graph_from_string(s) 9 | g = DirectedAdjacencyGraph.new(Array) 10 | s.split(/\n/).collect { |x| x.split(/->/) }.each do |a| 11 | from = a[0].strip 12 | a[1].split.each do |to| 13 | g.add_edge from, to 14 | end 15 | end 16 | g 17 | end 18 | 19 | class TestComponents < Test::Unit::TestCase 20 | 21 | def setup 22 | @dg = DirectedAdjacencyGraph.new(Array) 23 | edges = [[1, 2], [2, 3], [2, 4], [4, 5], [1, 6], [6, 4]] 24 | edges.each do |(src, target)| 25 | @dg.add_edge(src, target) 26 | end 27 | @bfs = @dg.bfs_iterator(1) 28 | @dfs = @dg.dfs_iterator(1) 29 | 30 | @ug = AdjacencyGraph.new(Array) 31 | @ug.add_edges(*edges) 32 | 33 | @dg2 = graph_from_string(<<-END 34 | a -> b f h 35 | b -> c a 36 | c -> d b 37 | d -> e 38 | e -> d 39 | f -> g 40 | g -> f d 41 | h -> i 42 | i -> h j e c 43 | END 44 | ) 45 | end 46 | 47 | def test_connected_components 48 | ccs = [] 49 | @ug.each_connected_component { |c| ccs << c } 50 | assert_equal(1, ccs.size) 51 | 52 | ccs = [] 53 | @ug.add_edge 10, 11 54 | @ug.add_edge 33, 44 55 | @ug.each_connected_component { |c| ccs << c.sort } 56 | assert_equal([[10, 11], [1, 2, 3, 4, 5, 6], [33, 44]].sort, ccs.sort) 57 | end 58 | 59 | def test_strong_components 60 | vis = @dg2.strongly_connected_components 61 | 62 | assert_equal(4, vis.num_comp) 63 | 64 | result = vis.comp_map.to_a.sort.reduce({}) { |res, a| 65 | if res.key?(a[1]) 66 | res[a[1]] << a[0] 67 | else 68 | res[a[1]] = [a[0]] 69 | end 70 | res 71 | } 72 | 73 | std_res = result.to_a.map { 74 | |a| 75 | [a[1][0], a[1]] 76 | }.sort 77 | 78 | assert_equal([["a", ["a", "b", "c", "h", "i"]], ["d", ["d", "e"]], ["f", ["f", "g"]], ["j", ["j"]]], std_res) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/cycles_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/adjacency' 4 | 5 | include RGL 6 | 7 | class TestCycles < Test::Unit::TestCase 8 | 9 | def setup 10 | @dg = DirectedAdjacencyGraph.new(Array) 11 | edges = [[1, 2], [2, 2], [2, 3], [3, 4], [4, 5], [5, 1], [6, 4], [6, 6], [1, 4], [7, 7], [7, 7]] 12 | edges.each do |(src, target)| 13 | @dg.add_edge(src, target) 14 | end 15 | 16 | @ug = AdjacencyGraph.new(Array) 17 | @ug.add_edges(*edges) 18 | end 19 | 20 | # Helper for testing for different permutations of a cycle 21 | def contains_cycle?(cycles, cycle) 22 | cycle.size.times do |i| 23 | return true if cycles.include?(cycle) 24 | cycle = cycle[1..-1] + [cycle[0]] 25 | end 26 | end 27 | 28 | def test_cycles 29 | d_cycles = @dg.cycles 30 | assert_equal 6, d_cycles.size 31 | assert d_cycles.include?([6]) 32 | assert d_cycles.include?([7]) 33 | assert d_cycles.include?([2]) 34 | assert contains_cycle?(d_cycles, [1, 4, 5]) 35 | assert contains_cycle?(d_cycles, [1, 2, 3, 4, 5]) 36 | 37 | assert_equal 5, DirectedAdjacencyGraph.new(Set, @dg).cycles.size 38 | 39 | u_cycles = AdjacencyGraph.new(Set, @dg).cycles.sort 40 | 41 | assert u_cycles.include?([2]) 42 | assert u_cycles.include?([6]) 43 | assert u_cycles.include?([7]) 44 | assert contains_cycle?(u_cycles, [1, 2, 3, 4, 5]) 45 | assert contains_cycle?(u_cycles, [1, 5, 4, 3, 2]) 46 | assert contains_cycle?(u_cycles, [1, 4, 3, 2]) 47 | assert contains_cycle?(u_cycles, [1, 4, 5]) 48 | assert contains_cycle?(u_cycles, [1, 5, 4]) 49 | assert contains_cycle?(u_cycles, [1, 5]) 50 | assert contains_cycle?(u_cycles, [1, 2]) 51 | assert contains_cycle?(u_cycles, [1, 2, 3, 4]) 52 | assert contains_cycle?(u_cycles, [2, 3]) 53 | assert contains_cycle?(u_cycles, [1, 4]) 54 | assert contains_cycle?(u_cycles, [3, 4]) 55 | assert contains_cycle?(u_cycles, [4, 5]) 56 | assert contains_cycle?(u_cycles, [4, 6]) 57 | assert_equal 16, u_cycles.size 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /test/dijkstra_issue24_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/dijkstra' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | class TestDijkstraIssue24 < Test::Unit::TestCase 9 | 10 | def setup 11 | @graph = RGL::AdjacencyGraph[2,53, 2,3, 3,8, 3,28, 3,39, 29,58, 8,35, 12,39, 10,29, 62,15, 15,32, 32,58, 58,44, 44,53] 12 | 13 | end 14 | 15 | def test_shortest_path_search 16 | assert_equal([53, 44, 58, 32, 15, 62], shortest_path(53, 62)) 17 | end 18 | 19 | def shortest_path(v,w) 20 | @graph.dijkstra_shortest_path(Hash.new(1), v, w) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/dijkstra_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/dijkstra' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | class TestDijkstra < Test::Unit::TestCase 9 | 10 | def setup 11 | @graph = AdjacencyGraph[1,2, 1,3, 2,3, 2,4, 3,4] 12 | 13 | @edge_weights = { 14 | [1, 2] => 10, 15 | [1, 3] => 1, 16 | [2, 3] => 1, 17 | [2, 4] => 1, 18 | [3, 4] => 10 19 | } 20 | 21 | @edge_weights_lambda = lambda { |edge| @edge_weights[edge] } 22 | end 23 | 24 | def test_shortest_path_search 25 | assert_equal([1, 3, 2, 4], shortest_path(1, 4)) 26 | end 27 | 28 | def test_shortest_path_search_with_lambda 29 | assert_equal([1, 3, 2, 4], shortest_path(1, 4, @edge_weights_lambda)) 30 | end 31 | 32 | def test_shortest_path_to_the_source 33 | assert_equal([1], shortest_path(1, 1)) 34 | end 35 | 36 | def test_path_for_unreachable_vertex 37 | @graph.add_vertex(5) 38 | assert_equal(nil, shortest_path(1, 5)) 39 | end 40 | 41 | def test_shortest_paths_search 42 | assert_equal( 43 | { 44 | 1 => [1], 45 | 2 => [1, 3, 2], 46 | 3 => [1, 3], 47 | 4 => [1, 3, 2, 4] 48 | }, 49 | shortest_paths(1) 50 | ) 51 | end 52 | 53 | def test_shortest_paths_search_with_lambda 54 | assert_equal( 55 | { 56 | 1 => [1], 57 | 2 => [1, 3, 2], 58 | 3 => [1, 3], 59 | 4 => [1, 3, 2, 4] 60 | }, 61 | shortest_paths(1, @edge_weights_lambda) 62 | ) 63 | end 64 | 65 | def test_shortest_paths_search_with_unreachable_vertex 66 | @graph.add_vertex(5) 67 | 68 | assert_equal( 69 | { 70 | 1 => [1], 71 | 2 => [1, 3, 2], 72 | 3 => [1, 3], 73 | 4 => [1, 3, 2, 4], 74 | 5 => nil 75 | }, 76 | shortest_paths(1) 77 | ) 78 | end 79 | 80 | def test_visitor 81 | visitor = DijkstraVisitor.new(@graph) 82 | 83 | events = [] 84 | 85 | %w[examine_vertex examine_edge edge_relaxed edge_not_relaxed finish_vertex].each do |event| 86 | visitor.send("set_#{event}_event_handler") { |*args| events << { event.to_sym => args } } 87 | end 88 | 89 | @graph.dijkstra_shortest_paths(@edge_weights, 1, visitor) 90 | 91 | assert_equal( 92 | [ 93 | { :examine_vertex => [1] }, 94 | { :examine_edge => [1, 2] }, 95 | { :edge_relaxed => [1, 2] }, 96 | { :examine_edge => [1, 3] }, 97 | { :edge_relaxed => [1, 3] }, 98 | { :finish_vertex => [1] }, 99 | { :examine_vertex => [3] }, 100 | { :examine_edge => [3, 2] }, 101 | { :edge_relaxed => [3, 2] }, 102 | { :examine_edge => [3, 4] }, 103 | { :edge_relaxed => [3, 4] }, 104 | { :finish_vertex => [3] }, 105 | { :examine_vertex => [2] }, 106 | { :examine_edge => [2, 4] }, 107 | { :edge_relaxed => [2, 4] }, 108 | { :finish_vertex => [2] }, 109 | { :examine_vertex => [4] }, 110 | { :finish_vertex => [4] }, 111 | ], 112 | events 113 | ) 114 | end 115 | 116 | def test_negative_edge_weight 117 | @edge_weights[[2, 3]] = -7 118 | assert_raises(ArgumentError, 'weight of edge (2, 3) is negative') { shortest_path(1, 5) } 119 | end 120 | 121 | def test_negative_edge_weight_with_lambda 122 | @edge_weights[[2, 3]] = -7 123 | assert_raises(ArgumentError, 'weight of edge (2, 3) is negative') { shortest_path(1, 5, @edge_weights_lambda) } 124 | end 125 | 126 | def test_missing_edge_weight 127 | @edge_weights.delete([2, 3]) 128 | assert_raises(ArgumentError, 'weight of edge (2, 3) is not defined') { shortest_path(1, 5) } 129 | end 130 | 131 | def test_edge_weights_map_object_in_argument 132 | weights_map = EdgePropertiesMap.new(@edge_weights, @graph.directed?) 133 | dijkstra = DijkstraAlgorithm.new(@graph, weights_map, DijkstraVisitor.new(@graph)) 134 | 135 | assert_equal([1, 3, 2, 4], dijkstra.shortest_path(1, 4)) 136 | end 137 | 138 | private 139 | 140 | def shortest_path(source, target, edge_weights = @edge_weights) 141 | @graph.dijkstra_shortest_path(edge_weights, source, target) 142 | end 143 | 144 | def shortest_paths(source, edge_weights = @edge_weights) 145 | @graph.dijkstra_shortest_paths(edge_weights, source) 146 | end 147 | 148 | end -------------------------------------------------------------------------------- /test/directed_graph_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/adjacency' 4 | 5 | include RGL 6 | include RGL::Edge 7 | 8 | class TestDirectedGraph < Test::Unit::TestCase 9 | def setup 10 | @dg = DirectedAdjacencyGraph.new 11 | [[1, 2], [2, 3], [3, 2], [2, 4]].each do |(src, target)| 12 | @dg.add_edge(src, target) 13 | end 14 | @gfa = DirectedAdjacencyGraph[1, 2, 3, 4] 15 | end 16 | 17 | def test_empty_graph 18 | dg = DirectedAdjacencyGraph.new 19 | assert dg.empty? 20 | assert dg.directed? 21 | assert(!dg.has_edge?(2, 1)) 22 | assert(!dg.has_vertex?(3)) 23 | # Non existent vertex result in a Name Error because each_key is 24 | # called for nil 25 | assert_raises(NoVertexError) { dg.out_degree(3) } 26 | assert_equal([], dg.vertices) 27 | assert_equal(0, dg.size) 28 | assert_equal(0, dg.num_vertices) 29 | assert_equal(0, dg.num_edges) 30 | assert_equal(DirectedEdge, dg.edge_class) 31 | assert([].eql?(dg.edges)) 32 | end 33 | 34 | def test_add 35 | dg = DirectedAdjacencyGraph.new 36 | dg.add_edge(1, 2) 37 | assert(!dg.empty?) 38 | assert(dg.has_edge?(1, 2)) 39 | assert(!dg.has_edge?(2, 1)) 40 | assert(dg.has_vertex?(1) && dg.has_vertex?(2)) 41 | assert(!dg.has_vertex?(3)) 42 | 43 | assert_equal([1, 2], dg.vertices.sort) 44 | assert([DirectedEdge.new(1, 2)].eql?(dg.edges)) 45 | assert_equal("(1-2)", dg.edges.join) 46 | 47 | assert_equal([2], dg.adjacent_vertices(1)) 48 | assert_equal([], dg.adjacent_vertices(2)) 49 | 50 | assert_equal(1, dg.out_degree(1)) 51 | assert_equal(0, dg.out_degree(2)) 52 | end 53 | 54 | def test_edges 55 | assert_equal(4, @dg.edges.length) 56 | assert_equal([1, 2, 2, 3], @dg.edges.map { |l| l.source }.sort) 57 | assert_equal([2, 2, 3, 4], @dg.edges.map { |l| l.target }.sort) 58 | assert_equal("(1-2)(2-3)(2-4)(3-2)", @dg.edges.map { |l| l.to_s }.sort.join) 59 | # assert_equal([0,1,2,3], @dg.edges.map {|l| l.info}.sort) 60 | end 61 | 62 | def test_vertices 63 | assert_equal([1, 2, 3, 4], @dg.vertices.sort) 64 | end 65 | 66 | def test_edges_from_to? 67 | assert @dg.has_edge?(1, 2) 68 | assert @dg.has_edge?(2, 3) 69 | assert @dg.has_edge?(3, 2) 70 | assert @dg.has_edge?(2, 4) 71 | assert !@dg.has_edge?(2, 1) 72 | assert !@dg.has_edge?(3, 1) 73 | assert !@dg.has_edge?(4, 1) 74 | assert !@dg.has_edge?(4, 2) 75 | end 76 | 77 | def test_remove_edges 78 | @dg.remove_edge 1, 2 79 | assert !@dg.has_edge?(1, 2) 80 | @dg.remove_edge 1, 2 81 | assert !@dg.has_edge?(1, 2) 82 | @dg.remove_vertex 3 83 | assert !@dg.has_vertex?(3) 84 | assert !@dg.has_edge?(2, 3) 85 | assert_equal('(2-4)', @dg.edges.join) 86 | end 87 | 88 | def test_add_vertices 89 | dg = DirectedAdjacencyGraph.new 90 | dg.add_vertices(1, 3, 2, 4) 91 | assert_equal(dg.vertices.sort, [1, 2, 3, 4]) 92 | 93 | dg.remove_vertices(1, 3) 94 | assert_equal(dg.vertices.sort, [2, 4]) 95 | end 96 | 97 | def test_creating_from_array 98 | assert_equal([1, 2, 3, 4], @gfa.vertices.sort) 99 | assert_equal('(1-2)(3-4)', @gfa.edges.join) 100 | end 101 | 102 | def test_creating_from_graphs 103 | @gfa.each_edge { |e| @dg.add_edge(e[0], e[1])} 104 | dg = DirectedAdjacencyGraph.new(Set, @dg, @gfa) 105 | assert_equal(dg.vertices.to_set, (@dg.vertices + @gfa.vertices).to_set) 106 | assert_equal(dg.edges.to_set, (@dg.edges + @gfa.edges).to_set) 107 | end 108 | 109 | def test_reverse 110 | # Add isolated vertex 111 | @dg.add_vertex(42) 112 | reverted = @dg.reverse 113 | 114 | @dg.each_edge do |u, v| 115 | assert(reverted.has_edge?(v, u)) 116 | end 117 | 118 | assert(reverted.has_vertex?(42), 'Reverted graph should contain isolated Vertex 42') 119 | end 120 | 121 | def test_to_undirected 122 | undirected = @dg.to_undirected 123 | assert_equal '(1=2)(2=3)(2=4)', undirected.edges.sort.join 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /test/dot_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/dot' 4 | require 'rgl/adjacency' 5 | 6 | class TestDot < Test::Unit::TestCase 7 | 8 | def assert_match(dot, pattern) 9 | assert(!(dot =~ pattern).nil?, "#{dot} doesn't match #{pattern}") 10 | end 11 | 12 | def test_to_dot_digraph 13 | graph = RGL::DirectedAdjacencyGraph["a", "b"] 14 | 15 | begin 16 | dot = graph.to_dot_graph.to_s 17 | 18 | first_vertex_id = "a" 19 | second_vertex_id = "b" 20 | 21 | assert_match(dot, /\{[^}]*\}/) # {...} 22 | assert_match(dot, /#{first_vertex_id}\s*\[/) # node 1 23 | assert_match(dot, /label\s*=\s*a/) # node 1 label 24 | assert_match(dot, /#{second_vertex_id}\s*\[/) # node 2 25 | assert_match(dot, /label\s*=\s*b/) # node 2 label 26 | assert_match(dot, /#{first_vertex_id}\s*->\s*#{second_vertex_id}/) # edge 27 | rescue 28 | puts "Graphviz not installed?" 29 | end 30 | end 31 | 32 | def test_dot_digraph_with_complicated_options 33 | graph = RGL::DirectedAdjacencyGraph['a','b', 'c','d', 'a','c'] 34 | 35 | graph.set_vertex_options('a', label: 'This is A', shape: 'box3d', fontcolor: 'green', fontsize: 16) 36 | graph.set_vertex_options('b', label: 'This is B', shape: 'tab', fontcolor: 'red', fontsize: 14) 37 | graph.set_vertex_options('c', shape: 'tab', fontcolor: 'blue') 38 | 39 | graph.set_edge_options('a', 'b', label: 'NotCapitalEdge', style: 'dotted', dir: 'back', color: 'magenta') 40 | graph.set_edge_options('a', 'c', weight: 5, color: 'blue') 41 | 42 | graph_options = { 43 | "rankdir" => "LR", 44 | "labelloc" => "t", 45 | "label" => "Graph\n (generated #{Time.now.utc})" 46 | } 47 | 48 | dot = graph.to_dot_graph(graph_options).to_s 49 | 50 | assert_match(dot, /labelloc = t\n\s*/) 51 | assert_match(dot, /rankdir = LR\n\s*/) 52 | assert_match(dot, /a \[\n\s*fontcolor = green,\n\s*fontsize = 16,\n\s*shape = box3d,\n\s*label = "This is A"\n\s*/) 53 | assert_match(dot, /b \[\n\s*fontcolor = red,\n\s*fontsize = 14,\n\s*shape = tab,\n\s*label = "This is B"\n\s*/) 54 | assert_match(dot, /a -> b \[\n\s*color = magenta,\n\s*dir = back,\n\s*fontsize = 8,\n\s*label = NotCapitalEdge,\n\s*style = dotted\n\s*/) 55 | end 56 | 57 | def test_to_dot_graph 58 | graph = RGL::AdjacencyGraph["a", "b"] 59 | def graph.vertex_label(v) 60 | "label-"+v.to_s 61 | end 62 | 63 | def graph.vertex_id(v) 64 | "id-"+v.to_s 65 | end 66 | graph.write_to_graphic_file 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/edge_properties_map_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/edge_properties_map' 4 | 5 | include RGL 6 | 7 | class TestEdgePropertiesMap < Test::Unit::TestCase 8 | 9 | def setup 10 | @edge_properties = { 11 | [1, 2] => 1, 12 | [2, 3] => 3, 13 | [1, 3] => 7 14 | } 15 | 16 | @edge_properties_lambda = lambda { |edge| @edge_properties[edge] } 17 | end 18 | 19 | def test_directed_graph 20 | properties_map = EdgePropertiesMap.new(@edge_properties, true) 21 | 22 | assert_equal(1, properties_map.edge_property(1, 2)) 23 | assert_equal(3, properties_map.edge_property(2, 3)) 24 | assert_equal(7, properties_map.edge_property(1, 3)) 25 | 26 | assert_raise ArgumentError do 27 | properties_map.edge_property(2, 1) 28 | end 29 | end 30 | 31 | def test_undirected_graph 32 | properties_map = EdgePropertiesMap.new(@edge_properties, false) 33 | 34 | assert_equal(1, properties_map.edge_property(1, 2)) 35 | assert_equal(3, properties_map.edge_property(2, 3)) 36 | assert_equal(7, properties_map.edge_property(1, 3)) 37 | 38 | assert_equal(1, properties_map.edge_property(2, 1)) 39 | assert_equal(3, properties_map.edge_property(3, 2)) 40 | assert_equal(7, properties_map.edge_property(3, 1)) 41 | end 42 | 43 | def test_nil_properties 44 | assert_raise ArgumentError do 45 | EdgePropertiesMap.new(@edge_properties.merge([1, 4] => nil), false) 46 | end 47 | end 48 | 49 | def test_non_negative_properties_map 50 | assert_raise ArgumentError do 51 | NonNegativeEdgePropertiesMap.new(@edge_properties.merge([1, 4] => -2), false) 52 | end 53 | end 54 | 55 | def test_with_lambda 56 | properties_map = EdgePropertiesMap.new(@edge_properties_lambda, true) 57 | 58 | assert_equal(1, properties_map.edge_property(1, 2)) 59 | assert_equal(3, properties_map.edge_property(2, 3)) 60 | assert_equal(7, properties_map.edge_property(1, 3)) 61 | end 62 | 63 | end -------------------------------------------------------------------------------- /test/edge_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/base' 4 | 5 | include RGL::Edge 6 | 7 | class TestEdge < Test::Unit::TestCase 8 | 9 | def test_directed_edge 10 | assert_raises(ArgumentError) { DirectedEdge.new } 11 | e = DirectedEdge.new 1, 2 12 | assert_equal(1, e.source) 13 | assert_equal(2, e.target) 14 | assert_equal([1, 2], e.to_a) 15 | assert_equal("(1-2)", e.to_s) 16 | assert_equal("(2-1)", e.reverse.to_s) 17 | assert_equal([1, 2], [e[0], e[1]]) 18 | assert(DirectedEdge[1, 2].eql?(DirectedEdge.new(1, 2))) 19 | assert(!DirectedEdge[1, 2].eql?(DirectedEdge.new(1, 3))) 20 | assert(!DirectedEdge[2, 1].eql?(DirectedEdge.new(1, 2))) 21 | end 22 | 23 | def test_undirected_edge 24 | assert_raises(ArgumentError) { UnDirectedEdge.new } 25 | e = UnDirectedEdge.new 1, 2 26 | assert_equal(1, e.source) 27 | assert_equal(2, e.target) 28 | assert_equal([1, 2], e.to_a) 29 | assert_equal("(1=2)", e.to_s) 30 | assert(UnDirectedEdge.new(1, 2).eql?(UnDirectedEdge.new(2, 1))) 31 | assert(!UnDirectedEdge.new(1, 3).eql?(UnDirectedEdge.new(2, 1))) 32 | assert_equal(UnDirectedEdge.new(1, 2).hash, UnDirectedEdge.new(1, 2).hash) 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /test/edmonds_karp_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/edmonds_karp' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | class TestEdmondsKarp < Test::Unit::TestCase 9 | 10 | def setup 11 | @capacities_map = { 12 | [1, 2] => 3, 13 | [1, 4] => 3, 14 | [2, 3] => 4, 15 | [3, 1] => 3, 16 | [3, 4] => 1, 17 | [3, 5] => 2, 18 | [4, 5] => 2, 19 | [4, 6] => 6, 20 | [5, 2] => 1, 21 | [5, 7] => 1, 22 | [6, 7] => 9 23 | } 24 | 25 | @graph = DirectedAdjacencyGraph[*@capacities_map.keys.flatten] 26 | 27 | add_reverse_edges(@graph, @capacities_map) 28 | 29 | @expected_flow = { 30 | [1, 2] => 2, [2, 1] => -2, 31 | [1, 4] => 3, [4, 1] => -3, 32 | [2, 3] => 2, [3, 2] => -2, 33 | [3, 4] => 1, [4, 3] => -1, 34 | [3, 5] => 1, [5, 3] => -1, 35 | [4, 5] => 0, [5, 4] => 0, 36 | [4, 6] => 4, [6, 4] => -4, 37 | [5, 7] => 1, [7, 5] => -1, 38 | [6, 7] => 4, [7, 6] => -4, 39 | } 40 | end 41 | 42 | def test_max_flow 43 | assert_equal(@expected_flow, maximum_flow(1, 7)) 44 | end 45 | 46 | def test_max_flow_with_lambda_capacities_map 47 | capacities_lambda = lambda { |edge| @capacities_map[edge] } 48 | assert_equal(@expected_flow, maximum_flow(1, 7), capacities_lambda) 49 | end 50 | 51 | def test_reverse_edges_validation 52 | @graph.remove_edge(2, 1) 53 | assert_raises(ArgumentError, 'reverse edge for (2, 1) is missing') { maximum_flow(1, 7) } 54 | end 55 | 56 | def test_missing_capacities_validation 57 | @capacities_map.delete([3, 5]) 58 | assert_raises(ArgumentError, 'capacity for edge (3, 5) is missing') { maximum_flow(1, 7) } 59 | end 60 | 61 | def test_negative_capacities_validation 62 | @capacities_map[[5, 2]] = -2 63 | assert_raises(ArgumentError, 'capacity of edge (5, 2) is negative') { maximum_flow(1, 7) } 64 | end 65 | 66 | def test_zero_reverse_capacities_validation 67 | @capacities_map[[7, 5]] = 1 68 | assert_raises(ArgumentError, 'either (7, 5) or (5, 7) should have 0 capacity') { maximum_flow(1, 7) } 69 | end 70 | 71 | def test_zero_capacities 72 | @capacities_map[[1, 5]] = 0 73 | @capacities_map[[5, 1]] = 0 74 | assert_equal(@expected_flow, maximum_flow(1, 7)) 75 | end 76 | 77 | def test_equal_source_and_sink 78 | assert_raises(ArgumentError, "source and sink can't be equal") { maximum_flow(1, 1) } 79 | end 80 | 81 | def test_directed_graph_validation 82 | graph = AdjacencyGraph.new 83 | graph.add_vertex(1) 84 | 85 | assert_raises(NotDirectedError, 'Edmonds-Karp algorithm can only be applied to a directed graph') { graph.maximum_flow({}, 1, 2) } 86 | end 87 | 88 | def test_unreachable_sink 89 | assert_equal({}, maximum_flow(1, 8)) 90 | end 91 | 92 | private 93 | 94 | def maximum_flow(source, sink, capacities_map = @capacities_map) 95 | @graph.maximum_flow(capacities_map, source, sink) 96 | end 97 | 98 | def add_reverse_edges(graph, capacities_map) 99 | capacities_map.keys.each do |(u, v)| 100 | graph.add_edge(v, u) 101 | capacities_map[[v, u]] = 0 102 | end 103 | end 104 | 105 | end 106 | -------------------------------------------------------------------------------- /test/graph_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/adjacency' 4 | 5 | include RGL 6 | 7 | class TestGraph < Test::Unit::TestCase 8 | 9 | class NotImplementedGraph 10 | include Graph 11 | end 12 | 13 | def setup 14 | @dg1 = DirectedAdjacencyGraph.new 15 | @edges = [[1, 2], [2, 3], [2, 4], [4, 5], [1, 6], [6, 4]] 16 | @edges.each do |(src, target)| 17 | @dg1.add_edge(src, target) 18 | end 19 | @loan_vertices = [7, 8, 9] 20 | @loan_vertices.each do |vertex| 21 | @dg1.add_vertex(vertex) 22 | end 23 | 24 | @dg2 = DirectedAdjacencyGraph[*@edges.flatten] 25 | @loan_vertices.each do |vertex| 26 | @dg2.add_vertex(vertex) 27 | end 28 | 29 | @ug = AdjacencyGraph.new(Array) 30 | @ug.add_edges(*@edges) 31 | @ug.add_vertices(*@loan_vertices) 32 | end 33 | 34 | def test_equality 35 | assert_equal @dg1, @dg1 36 | assert_equal @dg1, @dg1.dup 37 | assert_equal @ug, @ug.dup 38 | assert_not_equal @ug, @dg1 39 | assert_not_equal @dg1, @ug 40 | assert_not_equal @dg1, 42 41 | assert_equal @dg1, @dg2 42 | @dg1.add_vertex 42 43 | assert_not_equal @dg1, @dg2 44 | end 45 | 46 | def test_to_adjacency 47 | assert_equal @dg1, @dg1.to_adjacency 48 | assert_equal @ug, @ug.to_adjacency 49 | end 50 | 51 | def test_merge 52 | merge = DirectedAdjacencyGraph.new(Array, @dg1, @ug) 53 | assert_equal merge.num_edges, 12 54 | assert_equal merge.num_vertices, 9 55 | merge = DirectedAdjacencyGraph.new(Set, @dg1, @dg1) 56 | assert_equal merge.num_edges, 6 57 | assert_equal merge.num_vertices, 9 58 | end 59 | 60 | def test_set_edgelist_class 61 | edges = @dg1.edges 62 | @dg1.edgelist_class=Array 63 | assert_equal edges, @dg1.edges 64 | end 65 | 66 | # Test for issue #22 67 | def test_edges_to_s 68 | assert_equal @dg1.edges.sort.to_s, "[(1-2), (1-6), (2-3), (2-4), (4-5), (6-4)]" 69 | end 70 | 71 | def test_not_implemented 72 | graph = NotImplementedGraph.new 73 | assert_raise(NotImplementedError) { graph.each_vertex } 74 | assert_raise(NotImplementedError) { graph.each_adjacent(nil) } 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/graph_xml_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/graphxml' 4 | require 'rgl/adjacency' 5 | require 'rgl/topsort' 6 | require 'rgl/connected_components' 7 | 8 | include RGL 9 | 10 | class TestGraphXML < Test::Unit::TestCase 11 | NORTH_DIR = './examples/north/' 12 | 13 | def setup 14 | @stream = File.new(NORTH_DIR + "g.10.0.graphml") 15 | end 16 | 17 | def tear_down 18 | @stream.close 19 | end 20 | 21 | def test_graphxml 22 | @dg = DirectedAdjacencyGraph.new.from_graphxml(@stream).edges.sort.join 23 | assert_equal("(n0-n1)(n0-n2)(n0-n9)(n3-n4)(n4-n5)(n5-n7)(n8-n0)(n8-n3)(n8-n4)(n8-n5)(n8-n6)", @dg) 24 | end 25 | 26 | def test_north_graphs 27 | name, nnodes, nedges = '', 0, 0 28 | IO.foreach(NORTH_DIR + '/Graph.log') { 29 | |line| 30 | if /name:\s*(.*)\sformat: graphml\s+nodes: (\d+)\s+edges: (\d+)/ =~ line 31 | name, nnodes, nedges = $1, $2.to_i, $3.to_i 32 | end 33 | if name && /directed: (\w+).*acyclic: (\w+).*connected: (\w+).*biconnected: (\w+)\s+/ =~ line 34 | directed, acyclic, connected = $1, $2, $3 35 | File.open(NORTH_DIR + name + '.graphml') { 36 | |file| 37 | print '.'; $stdout.flush 38 | graph = (directed == 'true' ? DirectedAdjacencyGraph : AdjacencyGraph).new.from_graphxml(file) 39 | #graph.write_to_graphic_file 40 | assert_equal(nnodes, graph.num_vertices) 41 | assert_equal(nedges, graph.num_edges) 42 | assert_equal(acyclic, graph.acyclic?.to_s) 43 | 44 | num_comp = 0 45 | graph.to_undirected.each_connected_component { |x| num_comp += 1 } 46 | assert_equal(connected, (num_comp == 1).to_s) 47 | } 48 | end 49 | } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/implicit_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/implicit' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | class TestImplicit < Test::Unit::TestCase 9 | def setup 10 | @dg = DirectedAdjacencyGraph.new 11 | [[1, 2], [2, 3], [2, 4], [4, 5], [1, 6], [6, 4]].each do |(src, target)| 12 | @dg.add_edge(src, target) 13 | end 14 | 15 | @cycle = ImplicitGraph.new { |g| 16 | g.vertex_iterator { |b| 0.upto(4, &b) } 17 | g.adjacent_iterator { |x, b| b.call((x+1)%5) } 18 | g.directed = true 19 | } 20 | end 21 | 22 | def test_empty 23 | empty = ImplicitGraph.new 24 | assert(empty.empty?) 25 | assert_equal([], empty.edges) 26 | assert_equal([], empty.vertices) 27 | end 28 | 29 | def test_cycle 30 | assert(!@cycle.empty?) 31 | assert_equal([0, 1, 2, 3, 4], @cycle.vertices.sort) 32 | assert_equal("(0-1)(1-2)(2-3)(3-4)(4-0)", @cycle.edges.sort.join) 33 | end 34 | 35 | def test_vertex_filtered_graph 36 | fg = @cycle.vertices_filtered_by { |v| v%2 == 0 } 37 | assert_equal([0, 2, 4], fg.vertices.sort) 38 | assert_equal("(4-0)", fg.edges.sort.join) 39 | assert(fg.directed?) 40 | 41 | fg = @dg.vertices_filtered_by { |v| v%2 == 0 } 42 | assert_equal([2, 4, 6], fg.vertices.sort) 43 | assert_equal("(2-4)(6-4)", fg.edges.sort.join) 44 | assert(fg.directed?) 45 | end 46 | 47 | def test_edge_filtered_graph 48 | fg = @cycle.edges_filtered_by { |u, v| u+v > 3 } 49 | assert_equal(@cycle.vertices.sort, fg.vertices.sort) 50 | assert_equal("(2-3)(3-4)(4-0)", fg.edges.sort.join) 51 | assert(fg.directed?) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/path_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | require 'rgl/adjacency' 6 | require 'rgl/path' 7 | 8 | class TestPath < Test::Unit::TestCase 9 | include RGL 10 | 11 | def setup 12 | edges = [[1, 2], [2, 3], [2, 4], [4, 5], [6, 4], [1, 6]] 13 | @directed_graph, @undirected_graph = 14 | [DirectedAdjacencyGraph, AdjacencyGraph].map do |klass| 15 | graph = klass.new 16 | graph.add_edges(*edges) 17 | graph 18 | end 19 | end 20 | 21 | def test_path_for_directed_graph 22 | assert(@directed_graph.path?(1, 5)) 23 | end 24 | 25 | def test_path_for_undirected_graph 26 | assert(@undirected_graph.path?(1, 5)) 27 | end 28 | 29 | def test_inverse_path_for_directed_graph 30 | assert_equal(@directed_graph.path?(3, 1), false) 31 | end 32 | 33 | def test_inverse_path_for_undirected_graph 34 | assert(@undirected_graph.path?(3, 1)) 35 | end 36 | 37 | def test_path_for_directed_graph_wrong_source 38 | assert_equal(@directed_graph.path?(0, 1), false) 39 | end 40 | 41 | def test_path_for_undirected_graph_wrong_source 42 | assert_equal(@undirected_graph.path?(0, 1), false) 43 | end 44 | 45 | def test_path_for_directed_graph_wrong_target 46 | assert_equal(@directed_graph.path?(4, 0), false) 47 | end 48 | 49 | def test_path_for_undirected_graph_wrong_target 50 | assert_equal(@undirected_graph.path?(4, 0), false) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/prim_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/prim' 4 | require 'rgl/adjacency' 5 | 6 | include RGL 7 | 8 | class TestPrim < Test::Unit::TestCase 9 | 10 | def setup 11 | @graph = AdjacencyGraph[1,2, 1,3, 2,3, 2,4, 3,4] 12 | 13 | @edge_weights = { 14 | [1, 2] => 10, 15 | [1, 3] => 1, 16 | [2, 3] => 1, 17 | [2, 4] => 1, 18 | [3, 4] => 10 19 | } 20 | 21 | @edge_weights_lambda = lambda { |edge| @edge_weights[edge] } 22 | end 23 | 24 | def test_minimum_spanning_tree 25 | assert(minimum_spanning_tree.is_a?(AdjacencyGraph)) 26 | end 27 | 28 | def test_minimum_spanning_tree_edges 29 | assert_equal([[1, 3], [2, 3], [2, 4]], minimum_spanning_tree_edges) 30 | end 31 | 32 | def test_minimum_spanning_tree_for_disconnected_graph 33 | @graph.add_edge(5, 6) 34 | @graph.add_edge(6, 7) 35 | @edge_weights.merge!([5, 6] => 1, [6, 7] => 2) 36 | 37 | assert_equal([[1, 3], [2, 3], [2, 4]], minimum_spanning_tree_edges(@edge_weights, 1)) 38 | assert_equal([[5, 6], [6, 7]], minimum_spanning_tree_edges(@edge_weights, 5)) 39 | end 40 | 41 | def test_visitor 42 | visitor = DijkstraVisitor.new(@graph) 43 | 44 | events = [] 45 | 46 | %w[examine_vertex examine_edge edge_relaxed edge_not_relaxed finish_vertex].each do |event| 47 | visitor.send("set_#{event}_event_handler") { |*args| events << { event.to_sym => args } } 48 | end 49 | 50 | @graph.prim_minimum_spanning_tree(@edge_weights, 1, visitor) 51 | 52 | assert_equal( 53 | [ 54 | { :examine_vertex => [1] }, 55 | { :examine_edge => [1, 2] }, 56 | { :edge_relaxed => [1, 2] }, 57 | { :examine_edge => [1, 3] }, 58 | { :edge_relaxed => [1, 3] }, 59 | { :finish_vertex => [1] }, 60 | { :examine_vertex => [3] }, 61 | { :examine_edge => [3, 2] }, 62 | { :edge_relaxed => [3, 2] }, 63 | { :examine_edge => [3, 4] }, 64 | { :edge_relaxed => [3, 4] }, 65 | { :finish_vertex => [3] }, 66 | { :examine_vertex => [2] }, 67 | { :examine_edge => [2, 4] }, 68 | { :edge_relaxed => [2, 4] }, 69 | { :finish_vertex => [2] }, 70 | { :examine_vertex => [4] }, 71 | { :finish_vertex => [4] }, 72 | ], 73 | events 74 | ) 75 | end 76 | 77 | def test_negative_weights 78 | @edge_weights[[1, 3]] = -2 79 | @edge_weights[[2, 3]] = -2 80 | 81 | assert_equal([[1, 3], [2, 3], [2, 4]], minimum_spanning_tree_edges) 82 | end 83 | 84 | private 85 | 86 | def minimum_spanning_tree_edges(edge_weights = @edge_weights, start_vertex = nil) 87 | sorted_edges(@graph.prim_minimum_spanning_tree(edge_weights, start_vertex)) 88 | end 89 | 90 | def minimum_spanning_tree(edge_weights = @edge_weights) 91 | @graph.prim_minimum_spanning_tree(edge_weights) 92 | end 93 | 94 | def sorted_edges(graph) 95 | graph.edges.map { |e| [e.source, e.target].sort }.sort 96 | end 97 | 98 | end -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | require 'rubygems' 5 | require 'test/unit' 6 | 7 | # Some helper utilities used in test classes 8 | 9 | class Array 10 | # We need Array#add in test classes to be able to use Arrays as adjacency lists 11 | # This is needed to have ordered lists as neighbors in our test graphs. 12 | alias add push 13 | end 14 | -------------------------------------------------------------------------------- /test/transitivity_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/transitivity' 4 | 5 | include RGL 6 | 7 | class TestTransitiveClosure < Test::Unit::TestCase 8 | 9 | def setup 10 | @dg = DirectedAdjacencyGraph.new 11 | @dg.add_edges([1, 2], [2, 3], [2, 4], [4, 5], [1, 6], [6, 4]) 12 | @dg_tc = DirectedAdjacencyGraph.new 13 | @dg_tc.add_edges( 14 | [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], 15 | [2, 3], [2, 4], [2, 5], 16 | [4, 5], 17 | [6, 4], [6, 5] 18 | ) 19 | @dg_tr = DirectedAdjacencyGraph.new 20 | @dg_tr.add_edges( 21 | [1, 2], [1, 6], 22 | [2, 3], [2, 4], 23 | [4, 5], 24 | [6, 4] 25 | ) 26 | 27 | @dg_loner = @dg.dup 28 | @dg_loner.add_vertices(7, 8, 9) 29 | @dg_loner_tc = @dg_tc.dup 30 | @dg_loner_tc.add_vertices(7, 8, 9) 31 | @dg_loner_tr = @dg_tr.dup 32 | @dg_loner_tr.add_vertices(7, 8, 9) 33 | 34 | @dg_cyclic = DirectedAdjacencyGraph.new 35 | @dg_cyclic.add_edges( 36 | [1, 1], [1, 2], 37 | [2, 3], 38 | [3, 4], 39 | [4, 5], 40 | [5, 6], 41 | [6, 3] 42 | ) 43 | @dg_cyclic_tc = DirectedAdjacencyGraph.new 44 | @dg_cyclic_tc.add_edges( 45 | [1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], 46 | [2, 3], [2, 4], [2, 5], [2, 6], 47 | [3, 3], [3, 4], [3, 5], [3, 6], 48 | [4, 3], [4, 4], [4, 5], [4, 6], 49 | [5, 3], [5, 4], [5, 5], [5, 6], 50 | [6, 3], [6, 4], [6, 5], [6, 6] 51 | ) 52 | @dg_cyclic_tr = DirectedAdjacencyGraph.new 53 | @dg_cyclic_tr.add_edges( 54 | [1, 1], [1, 2], 55 | [2, 3], 56 | [3, 4], 57 | [4, 5], 58 | [5, 6], 59 | [6, 3] 60 | ) 61 | end 62 | 63 | def test_transitive_closure 64 | # A simple graph without cycles. 65 | assert_equal(@dg_tc, @dg.transitive_closure) 66 | 67 | # Iterative applications of transitive closure should return the same result 68 | # as a single application. 69 | assert_equal( 70 | @dg.transitive_closure, 71 | @dg.transitive_closure.transitive_closure 72 | ) 73 | 74 | # Compute for a graph containing vertices without edges. 75 | assert_equal(@dg_loner_tc, @dg_loner.transitive_closure) 76 | 77 | # Iterative applications of transitive closure should return the same result 78 | # as a single application. 79 | assert_equal( 80 | @dg_loner.transitive_closure, 81 | @dg_loner.transitive_closure.transitive_closure 82 | ) 83 | 84 | # Compute for a graph with cycles. 85 | assert_equal(@dg_cyclic_tc, @dg_cyclic.transitive_closure) 86 | 87 | # Iterative applications of transitive closure should return the same result 88 | # as a single application. 89 | assert_equal( 90 | @dg_cyclic.transitive_closure, 91 | @dg_cyclic.transitive_closure.transitive_closure 92 | ) 93 | end 94 | 95 | def test_transitive_closure_undirected 96 | assert_raises(NotDirectedError) { AdjacencyGraph.new.transitive_closure } 97 | end 98 | 99 | def test_transitive_reduction 100 | # A simple graph without cycles. 101 | assert_equal(@dg_tr, @dg.transitive_reduction) 102 | 103 | # Compute for a graph containing vertices without edges. 104 | assert_equal(@dg_loner_tr, @dg_loner.transitive_reduction) 105 | 106 | # Compute for a graph with cycles. 107 | assert_equal(@dg_cyclic_tr, @dg_cyclic.transitive_reduction) 108 | 109 | # Test that the transitive closure of a transitive reduction is the same as 110 | # the transitive closure of the original graph. 111 | assert_equal( 112 | @dg.transitive_closure, 113 | @dg.transitive_reduction.transitive_closure 114 | ) 115 | assert_equal( 116 | @dg_loner.transitive_closure, 117 | @dg_loner.transitive_reduction.transitive_closure 118 | ) 119 | assert_equal( 120 | @dg_cyclic.transitive_closure, 121 | @dg_cyclic.transitive_reduction.transitive_closure 122 | ) 123 | end 124 | 125 | def test_transitive_reduction_undirected 126 | assert_raises(NotDirectedError) { AdjacencyGraph.new.transitive_reduction } 127 | end 128 | end 129 | 130 | -------------------------------------------------------------------------------- /test/traversal_bfs_require.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | # Do not require rgl/adjacency ! 4 | require 'rgl/traversal' 5 | require 'rgl/implicit' 6 | 7 | include RGL 8 | 9 | # Cyclic graph with _n_ vertices. Need a concrete graph, that is not an AdjacencyGraph 10 | def cycle(n) 11 | RGL::ImplicitGraph.new { |g| 12 | g.vertex_iterator { |b| 0.upto(n - 1, &b) } 13 | g.adjacent_iterator { |x, b| b.call((x + 1) % n) } 14 | g.directed = true 15 | } 16 | end 17 | 18 | class TestAdjacencyNotRequired < Test::Unit::TestCase 19 | 20 | def setup 21 | @dg = cycle(4) 22 | end 23 | 24 | def test_bfs_search_tree 25 | # bfs_search_tree_from requires rgl/adjacency if not yet loaded. 26 | assert_equal("(1-2)(2-3)(3-0)", @dg.bfs_search_tree_from(1).edges.sort.join) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/undirected_graph_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require 'rgl/adjacency' 4 | 5 | include RGL 6 | include RGL::Edge 7 | 8 | class TestUnDirectedGraph < Test::Unit::TestCase 9 | 10 | def setup 11 | @dg = AdjacencyGraph.new 12 | [[1, 2], [2, 3], [3, 2], [2, 4]].each do |(src, target)| 13 | @dg.add_edge(src, target) 14 | end 15 | end 16 | 17 | def test_empty_graph 18 | dg = AdjacencyGraph.new 19 | assert(dg.empty?) 20 | assert(!dg.directed?) 21 | assert(!dg.has_edge?(2, 1)) 22 | assert(!dg.has_vertex?(3)) 23 | # Non existent vertex result in a Name Error because each_key is 24 | # called for nil 25 | assert_raises(NoVertexError) { dg.out_degree(3) } 26 | assert_equal([], dg.vertices) 27 | assert_equal(0, dg.size) 28 | assert_equal(0, dg.num_vertices) 29 | assert_equal(0, dg.num_edges) 30 | assert_equal(UnDirectedEdge, dg.edge_class) 31 | assert([].eql?(dg.edges)) 32 | assert([].eql?(dg.to_a)) 33 | end 34 | 35 | def test_add 36 | dg = AdjacencyGraph.new 37 | dg.add_edge(1, 2) 38 | assert(!dg.empty?) 39 | assert(dg.has_edge?(1, 2)) 40 | assert(dg.has_edge?(2, 1), "Backwards edge not included!") 41 | assert(dg.has_vertex?(1) && dg.has_vertex?(2)) 42 | assert(!dg.has_vertex?(3)) 43 | 44 | assert_equal([1, 2], dg.vertices.sort) 45 | assert([DirectedEdge.new(1, 2)].eql?(dg.edges)) 46 | assert_equal("(1=2)", dg.edges.join) 47 | 48 | assert_equal([2], dg.adjacent_vertices(1)) 49 | assert_equal([1], dg.adjacent_vertices(2)) 50 | 51 | assert_equal(1, dg.out_degree(1)) 52 | assert_equal(1, dg.out_degree(2)) 53 | end 54 | 55 | def test_edges 56 | assert_equal(3, @dg.edges.length) 57 | edges = [[1, 2], [2, 3], [2, 4]].map { |x| UnDirectedEdge.new(*x) } 58 | assert_equal(edges, @dg.edges.sort) 59 | # assert_equal([0,1,2,3], @dg.edges.map {|l| l.info}.sort) 60 | end 61 | 62 | def test_vertices 63 | assert_equal([1, 2, 3, 4], @dg.vertices.sort) 64 | end 65 | 66 | def test_edges_from_to? 67 | assert @dg.has_edge?(1, 2) 68 | assert @dg.has_edge?(2, 3) 69 | assert @dg.has_edge?(3, 2) 70 | assert @dg.has_edge?(2, 4) 71 | assert @dg.has_edge?(2, 1) 72 | assert !@dg.has_edge?(3, 1) 73 | assert !@dg.has_edge?(4, 1) 74 | assert @dg.has_edge?(4, 2) 75 | end 76 | 77 | def test_remove_edges 78 | @dg.remove_edge 1, 2 79 | assert !@dg.has_edge?(1, 2), "(1,2) should not be an edge any more." 80 | @dg.remove_edge 1, 2 81 | assert !@dg.has_edge?(2, 1) 82 | @dg.remove_vertex 3 83 | assert !@dg.has_vertex?(3), "3 should not be a vertex any more." 84 | assert !@dg.has_edge?(2, 3) 85 | assert_equal([UnDirectedEdge.new(2, 4)], @dg.edges) 86 | end 87 | 88 | def test_add_vertices 89 | dg = AdjacencyGraph.new 90 | dg.add_vertices 1, 3, 2, 4 91 | assert_equal dg.vertices.sort, [1, 2, 3, 4] 92 | 93 | dg.remove_vertices 1, 3 94 | assert_equal dg.vertices.sort, [2, 4] 95 | 96 | dg.remove_vertices 1, 3, Object # ones again 97 | assert_equal dg.vertices.sort, [2, 4] 98 | end 99 | 100 | def test_reverse 101 | assert_equal(@dg, @dg.reverse) 102 | end 103 | end 104 | --------------------------------------------------------------------------------